deploy: current vibn theia state
Made-with: Cursor
@@ -0,0 +1,117 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 EclipseSource and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { BackendStopwatch, CommandRegistry, Emitter, MeasurementOptions, OS } from '../common';
|
||||
import { ApplicationInfo, ApplicationServer, ExtensionInfo } from '../common/application-protocol';
|
||||
import { EnvVariable, EnvVariablesServer } from './../common/env-variables';
|
||||
import { bindMessageService } from '../browser/frontend-application-bindings';
|
||||
import { KeyStoreService } from '../common/key-store';
|
||||
import { QuickPickService } from '../common/quick-pick-service';
|
||||
import { QuickPickServiceImpl } from '../browser/quick-input';
|
||||
import { BackendRequestService, RequestService } from '@theia/request';
|
||||
import { ConnectionStatus, ConnectionStatusService } from '../browser/connection-status-service';
|
||||
|
||||
export { bindMessageService };
|
||||
|
||||
// is loaded directly after the regular frontend module
|
||||
export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
if (isBound(CommandRegistry)) {
|
||||
rebind(CommandRegistry).toSelf().inSingletonScope();
|
||||
} else {
|
||||
bind(CommandRegistry).toSelf().inSingletonScope();
|
||||
}
|
||||
|
||||
const stopwatch: BackendStopwatch = {
|
||||
start: async (_name: string, _options?: MeasurementOptions | undefined): Promise<number> => -1,
|
||||
stop: async (_measurement: number, _message: string, _messageArgs: unknown[]): Promise<void> => { }
|
||||
};
|
||||
if (isBound(BackendStopwatch)) {
|
||||
rebind(BackendStopwatch).toConstantValue(stopwatch);
|
||||
} else {
|
||||
bind(BackendStopwatch).toConstantValue(stopwatch);
|
||||
}
|
||||
|
||||
if (isBound(CommandRegistry)) {
|
||||
rebind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope();
|
||||
} else {
|
||||
bind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope();
|
||||
}
|
||||
|
||||
const mockedApplicationServer: ApplicationServer = {
|
||||
getExtensionsInfos: async (): Promise<ExtensionInfo[]> => [],
|
||||
getApplicationInfo: async (): Promise<ApplicationInfo | undefined> => undefined,
|
||||
getApplicationRoot: async (): Promise<string> => '',
|
||||
getApplicationPlatform: () => Promise.resolve('web'),
|
||||
getBackendOS: async (): Promise<OS.Type> => OS.Type.Linux
|
||||
};
|
||||
if (isBound(ApplicationServer)) {
|
||||
rebind(ApplicationServer).toConstantValue(mockedApplicationServer);
|
||||
} else {
|
||||
bind(ApplicationServer).toConstantValue(mockedApplicationServer);
|
||||
}
|
||||
|
||||
const varServer: EnvVariablesServer = {
|
||||
getExecPath: async (): Promise<string> => '',
|
||||
getVariables: async (): Promise<EnvVariable[]> => [],
|
||||
getValue: async (_key: string): Promise<EnvVariable | undefined> => undefined,
|
||||
getConfigDirUri: async (): Promise<string> => '',
|
||||
getHomeDirUri: async (): Promise<string> => '',
|
||||
getDrives: async (): Promise<string[]> => []
|
||||
};
|
||||
if (isBound(EnvVariablesServer)) {
|
||||
rebind(EnvVariablesServer).toConstantValue(varServer);
|
||||
} else {
|
||||
bind(EnvVariablesServer).toConstantValue(varServer);
|
||||
}
|
||||
|
||||
const keyStoreService: KeyStoreService = {
|
||||
deletePassword: () => Promise.resolve(false),
|
||||
findCredentials: () => Promise.resolve([]),
|
||||
findPassword: () => Promise.resolve(undefined),
|
||||
setPassword: () => Promise.resolve(),
|
||||
getPassword: () => Promise.resolve(undefined),
|
||||
keys: () => Promise.resolve([]),
|
||||
};
|
||||
if (isBound(KeyStoreService)) {
|
||||
rebind<KeyStoreService>(KeyStoreService).toConstantValue(keyStoreService);
|
||||
} else {
|
||||
bind<KeyStoreService>(KeyStoreService).toConstantValue(keyStoreService);
|
||||
}
|
||||
|
||||
const requestService: RequestService = {
|
||||
configure: () => Promise.resolve(),
|
||||
request: () => Promise.reject(),
|
||||
resolveProxy: () => Promise.resolve(undefined)
|
||||
};
|
||||
if (isBound(BackendRequestService)) {
|
||||
rebind<RequestService>(BackendRequestService).toConstantValue(requestService);
|
||||
} else {
|
||||
bind<RequestService>(BackendRequestService).toConstantValue(requestService);
|
||||
}
|
||||
|
||||
const connectionStatusService: ConnectionStatusService = {
|
||||
currentStatus: ConnectionStatus.ONLINE,
|
||||
onStatusChange: new Emitter<ConnectionStatus>().event
|
||||
};
|
||||
if (isBound(ConnectionStatusService)) {
|
||||
rebind<ConnectionStatusService>(ConnectionStatusService).toConstantValue(connectionStatusService);
|
||||
} else {
|
||||
bind<ConnectionStatusService>(ConnectionStatusService).toConstantValue(connectionStatusService);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 EclipseSource and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { AsyncLocalizationProvider, LanguageInfo, Localization } from '../../common/i18n/localization';
|
||||
import { LanguageQuickPickService } from '../../browser/i18n/language-quick-pick-service';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
const i18nMock: AsyncLocalizationProvider = {
|
||||
getCurrentLanguage: async (): Promise<string> => 'en',
|
||||
setCurrentLanguage: async (_languageId: string): Promise<void> => {
|
||||
|
||||
},
|
||||
getAvailableLanguages: async (): Promise<LanguageInfo[]> =>
|
||||
[]
|
||||
,
|
||||
loadLocalization: async (_languageId: string): Promise<Localization> => ({
|
||||
translations: {},
|
||||
languageId: 'en'
|
||||
})
|
||||
};
|
||||
bind(AsyncLocalizationProvider).toConstantValue(i18nMock);
|
||||
bind(LanguageQuickPickService).toSelf().inSingletonScope();
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 EclipseSource and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule, Container } from 'inversify';
|
||||
import { ILoggerServer, ILoggerClient, LogLevel, ConsoleLogger } from '../common/logger-protocol';
|
||||
import { ILogger, Logger, LoggerFactory, LoggerName } from '../common/logger';
|
||||
|
||||
// is loaded directly after the regular logger frontend module
|
||||
export const loggerFrontendOnlyModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
const logger: ILoggerServer = {
|
||||
setLogLevel: async (_name: string, _logLevel: number): Promise<void> => { },
|
||||
getLogLevel: async (_name: string): Promise<number> => LogLevel.INFO,
|
||||
log: async (name: string, logLevel: number, message: string, params: unknown[]): Promise<void> => {
|
||||
ConsoleLogger.log(name, logLevel, message, params);
|
||||
|
||||
},
|
||||
child: async (_name: string): Promise<void> => { },
|
||||
dispose: (): void => {
|
||||
},
|
||||
setClient: (_client: ILoggerClient | undefined): void => {
|
||||
}
|
||||
};
|
||||
if (isBound(ILoggerServer)) {
|
||||
rebind(ILoggerServer).toConstantValue(logger);
|
||||
} else {
|
||||
bind(ILoggerServer).toConstantValue(logger);
|
||||
}
|
||||
|
||||
if (isBound(ILoggerServer)) {
|
||||
rebind(LoggerFactory).toFactory(ctx =>
|
||||
(name: string) => {
|
||||
const child = new Container({ defaultScope: 'Singleton' });
|
||||
child.parent = ctx.container;
|
||||
child.bind(ILogger).to(Logger).inTransientScope();
|
||||
child.bind(LoggerName).toConstantValue(name);
|
||||
return child.get(ILogger);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
bind(LoggerFactory).toFactory(ctx =>
|
||||
(name: string) => {
|
||||
const child = new Container({ defaultScope: 'Singleton' });
|
||||
child.parent = ctx.container;
|
||||
child.bind(ILogger).to(Logger).inTransientScope();
|
||||
child.bind(LoggerName).toConstantValue(name);
|
||||
return child.get(ILogger);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 EclipseSource and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import { Event, RpcProxy, Channel, RpcProxyFactory, Emitter } from '../../common';
|
||||
import { injectable } from 'inversify';
|
||||
import { ServiceConnectionProvider } from '../../browser/messaging/service-connection-provider';
|
||||
import { ConnectionSource } from '../../browser/messaging/connection-source';
|
||||
|
||||
@injectable()
|
||||
export class FrontendOnlyConnectionSource implements ConnectionSource {
|
||||
onConnectionDidOpen = new Emitter<Channel>().event;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class FrontendOnlyServiceConnectionProvider extends ServiceConnectionProvider {
|
||||
onSocketDidOpen = Event.None;
|
||||
onSocketDidClose = Event.None;
|
||||
onIncomingMessageActivity = Event.None;
|
||||
override createProxy<T extends object>(path: unknown, target?: unknown): RpcProxy<T> {
|
||||
console.debug(`[Frontend-Only Fallback] Created proxy connection for ${path}`);
|
||||
const factory = target instanceof RpcProxyFactory ? target : new RpcProxyFactory<T>(target);
|
||||
return factory.createProxy();
|
||||
}
|
||||
override listen(path: string, handler: ServiceConnectionProvider.ConnectionHandler, reconnect: boolean): void {
|
||||
console.debug('[Frontend-Only Fallback] Listen to websocket connection requested');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 EclipseSource and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { WebSocketConnectionSource } from '../../browser/messaging/ws-connection-source';
|
||||
import { FrontendOnlyConnectionSource, FrontendOnlyServiceConnectionProvider } from './frontend-only-service-connection-provider';
|
||||
import { ConnectionSource } from '../../browser/messaging/connection-source';
|
||||
import { LocalConnectionProvider, RemoteConnectionProvider } from '../../browser/messaging/service-connection-provider';
|
||||
|
||||
// is loaded directly after the regular message frontend module
|
||||
export const messagingFrontendOnlyModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
unbind(WebSocketConnectionSource);
|
||||
bind(FrontendOnlyConnectionSource).toSelf().inSingletonScope();
|
||||
if (isBound(ConnectionSource)) {
|
||||
rebind(ConnectionSource).toService(FrontendOnlyConnectionSource);
|
||||
} else {
|
||||
bind(ConnectionSource).toService(FrontendOnlyConnectionSource);
|
||||
}
|
||||
bind(FrontendOnlyServiceConnectionProvider).toSelf().inSingletonScope();
|
||||
if (isBound(LocalConnectionProvider)) {
|
||||
rebind(LocalConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
|
||||
} else {
|
||||
bind(LocalConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
|
||||
}
|
||||
if (isBound(RemoteConnectionProvider)) {
|
||||
rebind(RemoteConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
|
||||
} else {
|
||||
bind(RemoteConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { LocalizationServer } from '../../common/i18n/localization-server';
|
||||
import { OS, OSBackendProvider } from '../../common/os';
|
||||
import { Localization } from '../../common/i18n/localization';
|
||||
|
||||
// loaded after regular preload module
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
const frontendOnlyLocalizationServer: LocalizationServer = {
|
||||
loadLocalization: async (languageId: string): Promise<Localization> => ({ translations: {}, languageId })
|
||||
};
|
||||
if (isBound(LocalizationServer)) {
|
||||
rebind(LocalizationServer).toConstantValue(frontendOnlyLocalizationServer);
|
||||
} else {
|
||||
bind(LocalizationServer).toConstantValue(frontendOnlyLocalizationServer);
|
||||
}
|
||||
|
||||
const frontendOnlyOSBackendProvider: OSBackendProvider = {
|
||||
getBackendOS: async (): Promise<OS.Type> => {
|
||||
if (window.navigator.platform.startsWith('Win')) {
|
||||
return OS.Type.Windows;
|
||||
} else if (window.navigator.platform.startsWith('Mac')) {
|
||||
return OS.Type.OSX;
|
||||
} else {
|
||||
return OS.Type.Linux;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isBound(OSBackendProvider)) {
|
||||
rebind(OSBackendProvider).toConstantValue(frontendOnlyOSBackendProvider);
|
||||
} else {
|
||||
bind(OSBackendProvider).toConstantValue(frontendOnlyOSBackendProvider);
|
||||
}
|
||||
});
|
||||
138
packages/core/src/browser/about-dialog.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from 'react';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { Dialog, DialogProps } from './dialogs';
|
||||
import { ReactDialog } from './dialogs/react-dialog';
|
||||
import { ApplicationServer, ApplicationInfo, ExtensionInfo } from '../common/application-protocol';
|
||||
import { Message } from './widgets/widget';
|
||||
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
|
||||
import { DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package/lib/api';
|
||||
import { WindowService } from './window/window-service';
|
||||
import { Key, KeyCode } from './keys';
|
||||
import { nls } from '../common/nls';
|
||||
|
||||
export const ABOUT_CONTENT_CLASS = 'theia-aboutDialog';
|
||||
export const ABOUT_EXTENSIONS_CLASS = 'theia-aboutExtensions';
|
||||
|
||||
@injectable()
|
||||
export class AboutDialogProps extends DialogProps {
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class AboutDialog extends ReactDialog<void> {
|
||||
protected applicationInfo: ApplicationInfo | undefined;
|
||||
protected extensionsInfos: ExtensionInfo[] = [];
|
||||
protected readonly okButton: HTMLButtonElement;
|
||||
|
||||
@inject(ApplicationServer)
|
||||
protected readonly appServer: ApplicationServer;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
|
||||
constructor(
|
||||
@inject(AboutDialogProps) protected override readonly props: AboutDialogProps
|
||||
) {
|
||||
super({
|
||||
title: FrontendApplicationConfigProvider.get().applicationName,
|
||||
});
|
||||
this.appendAcceptButton(Dialog.OK);
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.doInit();
|
||||
}
|
||||
|
||||
protected async doInit(): Promise<void> {
|
||||
this.applicationInfo = await this.appServer.getApplicationInfo();
|
||||
this.extensionsInfos = await this.appServer.getExtensionsInfos();
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected renderHeader(): React.ReactNode {
|
||||
const applicationInfo = this.applicationInfo;
|
||||
const compatibilityUrl = 'https://eclipse-theia.github.io/vscode-theia-comparator/status.html';
|
||||
|
||||
const detailsLabel = nls.localizeByDefault('Details');
|
||||
const versionLabel = nls.localizeByDefault('Version');
|
||||
const defaultApiLabel = nls.localize('theia/core/about/defaultApi', 'Default {0} API', 'VS Code');
|
||||
const compatibilityLabel = nls.localize('theia/core/about/compatibility', '{0} Compatibility', 'VS Code');
|
||||
|
||||
return <>
|
||||
<h3>{detailsLabel}</h3>
|
||||
<div className='about-details'>
|
||||
{applicationInfo && <p>{`${versionLabel}: ${applicationInfo.version}`}</p>}
|
||||
<p>{`${defaultApiLabel}: ${DEFAULT_SUPPORTED_API_VERSION}`}</p>
|
||||
<p>
|
||||
<a
|
||||
role={'button'}
|
||||
tabIndex={0}
|
||||
onClick={() => this.doOpenExternalLink(compatibilityUrl)}
|
||||
onKeyDown={(e: React.KeyboardEvent) => this.doOpenExternalLinkEnter(e, compatibilityUrl)}>
|
||||
{compatibilityLabel}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
protected renderExtensions(): React.ReactNode {
|
||||
const extensionsInfos = this.extensionsInfos;
|
||||
const listOfExtensions = nls.localize('theia/core/about/listOfExtensions', 'List of extensions');
|
||||
return <>
|
||||
<h3>{listOfExtensions}</h3>
|
||||
<ul className={ABOUT_EXTENSIONS_CLASS}>
|
||||
{
|
||||
extensionsInfos
|
||||
.sort((a: ExtensionInfo, b: ExtensionInfo) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
|
||||
.map((extension: ExtensionInfo) => <li key={extension.name}>{extension.name} {extension.version}</li>)
|
||||
}
|
||||
</ul>
|
||||
</>;
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return <div className={ABOUT_CONTENT_CLASS}>
|
||||
{this.renderHeader()}
|
||||
{this.renderExtensions()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a link in an external window.
|
||||
* @param url the link.
|
||||
*/
|
||||
protected doOpenExternalLink = (url: string) => this.windowService.openNewWindow(url, { external: true });
|
||||
protected doOpenExternalLinkEnter = (e: React.KeyboardEvent, url: string) => {
|
||||
if (this.isEnterKey(e)) {
|
||||
this.doOpenExternalLink(url);
|
||||
}
|
||||
};
|
||||
|
||||
protected isEnterKey(e: React.KeyboardEvent): boolean {
|
||||
return Key.ENTER.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode;
|
||||
}
|
||||
|
||||
get value(): undefined { return undefined; }
|
||||
}
|
||||
515
packages/core/src/browser/authentication-service.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 Red Hat, Inc. and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// code copied and modified from https://github.com/microsoft/vscode/blob/1.47.3/src/vs/workbench/services/authentication/browser/authenticationService.ts
|
||||
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Emitter, Event } from '../common/event';
|
||||
import { StorageService } from '../browser/storage-service';
|
||||
import { Disposable, DisposableCollection } from '../common/disposable';
|
||||
import { ACCOUNTS_MENU, ACCOUNTS_SUBMENU, MenuModelRegistry } from '../common/menu';
|
||||
import { Command, CommandRegistry } from '../common/command';
|
||||
import { nls } from '../common/nls';
|
||||
|
||||
export interface AuthenticationSessionAccountInformation {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
}
|
||||
export interface AuthenticationProviderSessionOptions {
|
||||
/**
|
||||
* The account that is being asked about. If this is passed in, the provider should
|
||||
* attempt to return the sessions that are only related to this account.
|
||||
*/
|
||||
account?: AuthenticationSessionAccountInformation;
|
||||
}
|
||||
|
||||
export interface AuthenticationSession {
|
||||
id: string;
|
||||
accessToken: string;
|
||||
idToken?: string;
|
||||
account: AuthenticationSessionAccountInformation;
|
||||
scopes: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface AuthenticationProviderInformation {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface AuthenticationWwwAuthenticateRequest {
|
||||
readonly wwwAuthenticate: string;
|
||||
readonly fallbackScopes?: readonly string[];
|
||||
}
|
||||
|
||||
export function isAuthenticationWwwAuthenticateRequest(obj: unknown): obj is AuthenticationWwwAuthenticateRequest {
|
||||
return !!(obj
|
||||
&& typeof obj === 'object'
|
||||
&& 'wwwAuthenticate' in obj
|
||||
&& (typeof obj.wwwAuthenticate === 'string'));
|
||||
}
|
||||
|
||||
/** Should match the definition from the theia/vscode types */
|
||||
export interface AuthenticationProviderAuthenticationSessionsChangeEvent {
|
||||
readonly added: readonly AuthenticationSession[] | undefined;
|
||||
readonly removed: readonly AuthenticationSession[] | undefined;
|
||||
readonly changed: readonly AuthenticationSession[] | undefined;
|
||||
}
|
||||
|
||||
// OAuth2 spec prohibits space in a scope, so use that to join them.
|
||||
const SCOPESLIST_SEPARATOR = ' ';
|
||||
|
||||
export interface SessionRequest {
|
||||
disposables: Disposable[];
|
||||
requestingExtensionIds: string[];
|
||||
}
|
||||
|
||||
export interface SessionRequestInfo {
|
||||
[scopes: string]: SessionRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our authentication provider should at least contain the following information:
|
||||
* - The signature of authentication providers from vscode
|
||||
* - Registration information about the provider (id, label)
|
||||
* - Provider options (supportsMultipleAccounts)
|
||||
*
|
||||
* Additionally, we provide the possibility to sign out of a specific account name.
|
||||
*/
|
||||
export interface AuthenticationProvider {
|
||||
id: string;
|
||||
|
||||
label: string;
|
||||
|
||||
supportsMultipleAccounts: boolean;
|
||||
|
||||
hasSessions(): boolean;
|
||||
|
||||
signOut(accountName: string): Promise<void>;
|
||||
|
||||
updateSessionItems(event: AuthenticationProviderAuthenticationSessionsChangeEvent): Promise<void>;
|
||||
|
||||
/**
|
||||
* An [event](#Event) which fires when the array of sessions has changed, or data
|
||||
* within a session has changed.
|
||||
*/
|
||||
readonly onDidChangeSessions: Event<AuthenticationProviderAuthenticationSessionsChangeEvent>;
|
||||
|
||||
/**
|
||||
* Get a list of sessions.
|
||||
* @param scopeListOrRequest Optional scope list of permissions requested or WWW-Authenticate request.
|
||||
* @param account The optional account that you would like to get the session for
|
||||
* @returns A promise that resolves to an array of authentication sessions.
|
||||
*/
|
||||
getSessions(
|
||||
scopeListOrRequest?: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
account?: AuthenticationSessionAccountInformation
|
||||
): Thenable<ReadonlyArray<AuthenticationSession>>;
|
||||
|
||||
/**
|
||||
* Prompts a user to login.
|
||||
* @param scopeListOrRequest A scope list of permissions requested or a WWW-Authenticate request.
|
||||
* @param options The options for createing the session
|
||||
* @returns A promise that resolves to an authentication session.
|
||||
*/
|
||||
createSession(
|
||||
scopeListOrRequest: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
options: AuthenticationProviderSessionOptions
|
||||
): Thenable<AuthenticationSession>;
|
||||
|
||||
/**
|
||||
* Removes the session corresponding to session id.
|
||||
* @param sessionId The id of the session to remove.
|
||||
*/
|
||||
removeSession(sessionId: string): Thenable<void>;
|
||||
}
|
||||
export const AuthenticationService = Symbol('AuthenticationService');
|
||||
|
||||
export interface AuthenticationService {
|
||||
isAuthenticationProviderRegistered(id: string): boolean;
|
||||
getProviderIds(): string[];
|
||||
registerAuthenticationProvider(id: string, provider: AuthenticationProvider): void;
|
||||
unregisterAuthenticationProvider(id: string): void;
|
||||
requestNewSession(id: string, scopeListOrRequest: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string): void;
|
||||
updateSessions(providerId: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent): void;
|
||||
|
||||
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
|
||||
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
|
||||
|
||||
readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent }>;
|
||||
readonly onDidUpdateSignInCount: Event<number>;
|
||||
getSessions(
|
||||
providerId: string,
|
||||
scopeListOrRequest?: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
user?: AuthenticationSessionAccountInformation
|
||||
): Promise<ReadonlyArray<AuthenticationSession>>;
|
||||
getLabel(providerId: string): string;
|
||||
supportsMultipleAccounts(providerId: string): boolean;
|
||||
login(
|
||||
providerId: string,
|
||||
scopeListOrRequest: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
options?: AuthenticationProviderSessionOptions
|
||||
): Promise<AuthenticationSession>;
|
||||
logout(providerId: string, sessionId: string): Promise<void>;
|
||||
|
||||
signOutOfAccount(providerId: string, accountName: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface SessionChangeEvent {
|
||||
providerId: string,
|
||||
label: string,
|
||||
event: AuthenticationProviderAuthenticationSessionsChangeEvent
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class AuthenticationServiceImpl implements AuthenticationService {
|
||||
private noAccountsMenuItem: Disposable | undefined;
|
||||
private noAccountsCommand: Command = { id: 'noAccounts' };
|
||||
private signInRequestItems = new Map<string, SessionRequestInfo>();
|
||||
private sessionMap = new Map<string, DisposableCollection>();
|
||||
|
||||
protected authenticationProviders: Map<string, AuthenticationProvider> = new Map<string, AuthenticationProvider>();
|
||||
protected authenticationProviderDisposables: Map<string, DisposableCollection> = new Map<string, DisposableCollection>();
|
||||
|
||||
private readonly onDidRegisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
|
||||
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidRegisterAuthenticationProviderEmitter.event;
|
||||
|
||||
private readonly onDidUnregisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
|
||||
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidUnregisterAuthenticationProviderEmitter.event;
|
||||
|
||||
private readonly onDidChangeSessionsEmitter: Emitter<SessionChangeEvent> = new Emitter<SessionChangeEvent>();
|
||||
readonly onDidChangeSessions: Event<SessionChangeEvent> = this.onDidChangeSessionsEmitter.event;
|
||||
|
||||
private readonly onDidChangeSignInCountEmitter: Emitter<number> = new Emitter<number>();
|
||||
readonly onDidUpdateSignInCount: Event<number> = this.onDidChangeSignInCountEmitter.event;
|
||||
|
||||
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
|
||||
@inject(CommandRegistry) protected readonly commands: CommandRegistry;
|
||||
@inject(StorageService) protected readonly storageService: StorageService;
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.onDidChangeSessions(event => this.handleSessionChange(event));
|
||||
this.commands.registerCommand(this.noAccountsCommand, {
|
||||
execute: () => { },
|
||||
isEnabled: () => false
|
||||
});
|
||||
}
|
||||
|
||||
protected async handleSessionChange(changeEvent: SessionChangeEvent): Promise<void> {
|
||||
if (changeEvent.event.added && changeEvent.event.added.length > 0) {
|
||||
const sessions = await this.getSessions(changeEvent.providerId);
|
||||
sessions.forEach(session => {
|
||||
if (!this.sessionMap.get(session.id)) {
|
||||
this.sessionMap.set(session.id, this.createAccountUi(changeEvent.providerId, changeEvent.label, session));
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const removed of changeEvent.event.removed || []) {
|
||||
const sessionId = typeof removed === 'string' ? removed : removed?.id;
|
||||
if (sessionId) {
|
||||
this.sessionMap.get(sessionId)?.dispose();
|
||||
this.sessionMap.delete(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected createAccountUi(providerId: string, providerLabel: string, session: AuthenticationSession): DisposableCollection {
|
||||
// unregister old commands and menus if present (there is only one per account but there may be several sessions per account)
|
||||
const providerAccountId = `account-sign-out-${providerId}-${session.account.id}`;
|
||||
this.commands.unregisterCommand(providerAccountId);
|
||||
|
||||
const providerAccountSubmenu = [...ACCOUNTS_SUBMENU, providerAccountId];
|
||||
this.menus.unregisterMenuAction({ commandId: providerAccountId }, providerAccountSubmenu);
|
||||
|
||||
// register new command and menu entry for the sessions account
|
||||
const disposables = new DisposableCollection();
|
||||
disposables.push(this.commands.registerCommand({ id: providerAccountId }, {
|
||||
execute: async () => {
|
||||
this.signOutOfAccount(providerId, session.account.label);
|
||||
}
|
||||
}));
|
||||
this.menus.registerSubmenu(providerAccountSubmenu, `${session.account.label} (${providerLabel})`);
|
||||
disposables.push(this.menus.registerMenuAction(providerAccountSubmenu, {
|
||||
label: nls.localizeByDefault('Sign Out'),
|
||||
commandId: providerAccountId
|
||||
}));
|
||||
return disposables;
|
||||
}
|
||||
|
||||
getProviderIds(): string[] {
|
||||
const providerIds: string[] = [];
|
||||
this.authenticationProviders.forEach(provider => {
|
||||
providerIds.push(provider.id);
|
||||
});
|
||||
return providerIds;
|
||||
}
|
||||
|
||||
isAuthenticationProviderRegistered(id: string): boolean {
|
||||
return this.authenticationProviders.has(id);
|
||||
}
|
||||
|
||||
private updateAccountsMenuItem(): void {
|
||||
let hasSession = false;
|
||||
this.authenticationProviders.forEach(async provider => {
|
||||
hasSession = hasSession || provider.hasSessions();
|
||||
});
|
||||
|
||||
if (hasSession && this.noAccountsMenuItem) {
|
||||
this.noAccountsMenuItem.dispose();
|
||||
this.noAccountsMenuItem = undefined;
|
||||
}
|
||||
|
||||
if (!hasSession && !this.noAccountsMenuItem) {
|
||||
this.noAccountsMenuItem = this.menus.registerMenuAction(ACCOUNTS_MENU, {
|
||||
label: 'You are not signed in to any accounts',
|
||||
order: '0',
|
||||
commandId: this.noAccountsCommand.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerAuthenticationProvider(id: string, authenticationProvider: AuthenticationProvider): void {
|
||||
if (this.authenticationProviders.get(id)) {
|
||||
throw new Error(`An authentication provider with id '${id}' is already registered.`);
|
||||
}
|
||||
this.authenticationProviders.set(id, authenticationProvider);
|
||||
|
||||
const disposables = new DisposableCollection();
|
||||
disposables.push(authenticationProvider.onDidChangeSessions(e => this.updateSessions(authenticationProvider.id, e)));
|
||||
this.authenticationProviderDisposables.set(id, disposables);
|
||||
|
||||
this.onDidRegisterAuthenticationProviderEmitter.fire({ id, label: authenticationProvider.label });
|
||||
|
||||
this.updateAccountsMenuItem();
|
||||
console.log(`An authentication provider with id '${id}' was registered.`);
|
||||
}
|
||||
|
||||
unregisterAuthenticationProvider(id: string): void {
|
||||
const provider = this.authenticationProviders.get(id);
|
||||
if (provider) {
|
||||
this.authenticationProviders.delete(id);
|
||||
this.onDidUnregisterAuthenticationProviderEmitter.fire({ id, label: provider.label });
|
||||
this.updateAccountsMenuItem();
|
||||
} else {
|
||||
console.error(`Failed to unregister an authentication provider. A provider with id '${id}' was not found.`);
|
||||
}
|
||||
this.authenticationProviderDisposables.get(id)?.dispose();
|
||||
this.authenticationProviderDisposables.delete(id);
|
||||
}
|
||||
|
||||
async updateSessions(id: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent): Promise<void> {
|
||||
const provider = this.authenticationProviders.get(id);
|
||||
if (provider) {
|
||||
await provider.updateSessionItems(event);
|
||||
this.onDidChangeSessionsEmitter.fire({ providerId: id, label: provider.label, event: event });
|
||||
this.updateAccountsMenuItem();
|
||||
|
||||
if (event.added) {
|
||||
await this.updateNewSessionRequests(provider);
|
||||
}
|
||||
} else {
|
||||
console.error(`Failed to update an authentication session. An authentication provider with id '${id}' was not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateNewSessionRequests(provider: AuthenticationProvider): Promise<void> {
|
||||
const existingRequestsForProvider = this.signInRequestItems.get(provider.id);
|
||||
if (!existingRequestsForProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousSize = this.signInRequestItems.size;
|
||||
const sessions = await provider.getSessions(undefined);
|
||||
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
|
||||
if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
|
||||
const sessionRequest = existingRequestsForProvider[requestedScopes];
|
||||
if (sessionRequest) {
|
||||
sessionRequest.disposables.forEach(item => item.dispose());
|
||||
}
|
||||
|
||||
delete existingRequestsForProvider[requestedScopes];
|
||||
if (Object.keys(existingRequestsForProvider).length === 0) {
|
||||
this.signInRequestItems.delete(provider.id);
|
||||
} else {
|
||||
this.signInRequestItems.set(provider.id, existingRequestsForProvider);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (previousSize !== this.signInRequestItems.size) {
|
||||
this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
|
||||
}
|
||||
}
|
||||
|
||||
async requestNewSession(
|
||||
providerId: string,
|
||||
scopeListOrRequest: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
extensionId: string,
|
||||
extensionName: string
|
||||
): Promise<void> {
|
||||
let provider = this.authenticationProviders.get(providerId);
|
||||
if (!provider) {
|
||||
// Activate has already been called for the authentication provider, but it cannot block on registering itself
|
||||
// since this is sync and returns a disposable. So, wait for registration event to fire that indicates the
|
||||
// provider is now in the map.
|
||||
await new Promise<void>((resolve, _) => {
|
||||
this.onDidRegisterAuthenticationProvider(e => {
|
||||
if (e.id === providerId) {
|
||||
provider = this.authenticationProviders.get(providerId);
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
const providerRequests = this.signInRequestItems.get(providerId);
|
||||
const signInRequestKey = isAuthenticationWwwAuthenticateRequest(scopeListOrRequest)
|
||||
? `${scopeListOrRequest.wwwAuthenticate}:${scopeListOrRequest.fallbackScopes?.join(SCOPESLIST_SEPARATOR) ?? ''}`
|
||||
: `${scopeListOrRequest.join(SCOPESLIST_SEPARATOR)}`;
|
||||
const extensionHasExistingRequest = providerRequests
|
||||
&& providerRequests[signInRequestKey]
|
||||
&& providerRequests[signInRequestKey].requestingExtensionIds.indexOf(extensionId) > -1;
|
||||
|
||||
if (extensionHasExistingRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuItem = this.menus.registerMenuAction(ACCOUNTS_SUBMENU, {
|
||||
label: nls.localizeByDefault('Sign in with {0} to use {1} (1)', provider.label, extensionName),
|
||||
order: '1',
|
||||
commandId: `${extensionId}signIn`,
|
||||
});
|
||||
|
||||
const signInCommand = this.commands.registerCommand({ id: `${extensionId}signIn` }, {
|
||||
execute: async () => {
|
||||
const session = await this.login(providerId, scopeListOrRequest);
|
||||
|
||||
// Add extension to allow list since user explicitly signed in on behalf of it
|
||||
const allowList = await readAllowedExtensions(this.storageService, providerId, session.account.label);
|
||||
if (!allowList.find(allowed => allowed.id === extensionId)) {
|
||||
allowList.push({ id: extensionId, name: extensionName });
|
||||
this.storageService.setData(`authentication-trusted-extensions-${providerId}-${session.account.label}`, JSON.stringify(allowList));
|
||||
}
|
||||
|
||||
// And also set it as the preferred account for the extension
|
||||
this.storageService.setData(`authentication-session-${extensionName}-${providerId}`, session.id);
|
||||
}
|
||||
});
|
||||
|
||||
const previousSize = this.signInRequestItems.size;
|
||||
if (providerRequests) {
|
||||
const existingRequest = providerRequests[signInRequestKey] || { disposables: [], requestingExtensionIds: [] };
|
||||
|
||||
providerRequests[signInRequestKey] = {
|
||||
disposables: [...existingRequest.disposables, menuItem, signInCommand],
|
||||
requestingExtensionIds: [...existingRequest.requestingExtensionIds, extensionId]
|
||||
};
|
||||
this.signInRequestItems.set(providerId, providerRequests);
|
||||
} else {
|
||||
this.signInRequestItems.set(providerId, {
|
||||
[signInRequestKey]: {
|
||||
disposables: [menuItem, signInCommand],
|
||||
requestingExtensionIds: [extensionId]
|
||||
}
|
||||
});
|
||||
}
|
||||
if (previousSize !== this.signInRequestItems.size) {
|
||||
this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLabel(id: string): string {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.label;
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
supportsMultipleAccounts(id: string): boolean {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.supportsMultipleAccounts;
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(id: string, scopes?: string[], user?: AuthenticationSessionAccountInformation): Promise<ReadonlyArray<AuthenticationSession>> {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.getSessions(scopes, user);
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
async login(
|
||||
id: string,
|
||||
scopeListOrRequest: ReadonlyArray<string> | AuthenticationWwwAuthenticateRequest,
|
||||
options?: AuthenticationProviderSessionOptions
|
||||
): Promise<AuthenticationSession> {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.createSession(scopeListOrRequest, options || {});
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
async logout(id: string, sessionId: string): Promise<void> {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.removeSession(sessionId);
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
async signOutOfAccount(id: string, accountName: string): Promise<void> {
|
||||
const authProvider = this.authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.signOut(accountName);
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AllowedExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function readAllowedExtensions(storageService: StorageService, providerId: string, accountName: string): Promise<AllowedExtension[]> {
|
||||
let trustedExtensions: AllowedExtension[] = [];
|
||||
try {
|
||||
const trustedExtensionSrc: string | undefined = await storageService.getData(`authentication-trusted-extensions-${providerId}-${accountName}`);
|
||||
if (trustedExtensionSrc) {
|
||||
trustedExtensions = JSON.parse(trustedExtensionSrc);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return trustedExtensions;
|
||||
}
|
||||
44
packages/core/src/browser/badges/badge-service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 EclipseSource GmbH and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from 'inversify';
|
||||
import { Emitter, Event } from '../../common';
|
||||
import { Widget } from '../widgets';
|
||||
|
||||
export interface Badge {
|
||||
value: number;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BadgeService {
|
||||
protected readonly badges = new WeakMap<Widget, Badge>();
|
||||
protected readonly onDidChangeBadgesEmitter = new Emitter<Widget>();
|
||||
get onDidChangeBadges(): Event<Widget> { return this.onDidChangeBadgesEmitter.event; }
|
||||
|
||||
getBadge(widget: Widget): Badge | undefined {
|
||||
return this.badges.get(widget);
|
||||
}
|
||||
|
||||
showBadge(widget: Widget, badge?: Badge): void {
|
||||
if (badge) {
|
||||
this.badges.set(widget, badge);
|
||||
this.onDidChangeBadgesEmitter.fire(widget);
|
||||
} else if (this.badges.delete(widget)) {
|
||||
this.onDidChangeBadgesEmitter.fire(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
packages/core/src/browser/badges/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 EclipseSource GmbH and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './badge-service';
|
||||
export * from './tabbar-badge-decorator';
|
||||
63
packages/core/src/browser/badges/tabbar-badge-decorator.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 EclipseSource GmbH and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, interfaces } from 'inversify';
|
||||
import { ViewContainer } from '../view-container';
|
||||
import { WidgetDecoration } from '../widget-decoration';
|
||||
import { Title, Widget } from '../widgets';
|
||||
import { TabBarDecorator } from '../shell/tab-bar-decorator';
|
||||
import { Disposable, Event } from '../../common';
|
||||
import { Badge, BadgeService } from './badge-service';
|
||||
|
||||
@injectable()
|
||||
export class TabBarBadgeDecorator implements TabBarDecorator {
|
||||
readonly id = 'theia-plugin-view-container-badge-decorator';
|
||||
|
||||
@inject(BadgeService)
|
||||
protected readonly badgeService: BadgeService;
|
||||
|
||||
onDidChangeDecorations(...[cb, thisArg, disposable]: Parameters<Event<void>>): Disposable { return this.badgeService.onDidChangeBadges(() => cb(), thisArg, disposable); }
|
||||
|
||||
decorate({ owner }: Title<Widget>): WidgetDecoration.Data[] {
|
||||
let total = 0;
|
||||
const result: WidgetDecoration.Data[] = [];
|
||||
const aggregate = (badge?: Badge) => {
|
||||
if (badge?.value) {
|
||||
total += badge.value;
|
||||
}
|
||||
if (badge?.tooltip) {
|
||||
result.push({ tooltip: badge.tooltip });
|
||||
}
|
||||
};
|
||||
if (owner instanceof ViewContainer) {
|
||||
for (const { wrapped } of owner.getParts()) {
|
||||
aggregate(this.badgeService.getBadge(wrapped));
|
||||
}
|
||||
} else {
|
||||
aggregate(this.badgeService.getBadge(owner));
|
||||
}
|
||||
if (total !== 0) {
|
||||
result.push({ badge: total });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function bindBadgeDecoration(bind: interfaces.Bind): void {
|
||||
bind(BadgeService).toSelf().inSingletonScope();
|
||||
bind(TabBarBadgeDecorator).toSelf().inSingletonScope();
|
||||
bind(TabBarDecorator).toService(TabBarBadgeDecorator);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, postConstruct } from '../../../shared/inversify';
|
||||
import { Emitter, Event } from '../../common';
|
||||
import { Disposable, DisposableCollection } from '../../common/disposable';
|
||||
import { Coordinate } from '../context-menu-renderer';
|
||||
import { RendererHost } from '../widgets/react-renderer';
|
||||
import { Styles } from './breadcrumbs-constants';
|
||||
|
||||
export interface BreadcrumbPopupContainerFactory {
|
||||
(parent: HTMLElement, breadcrumbId: string, position: Coordinate): BreadcrumbPopupContainer;
|
||||
}
|
||||
export const BreadcrumbPopupContainerFactory = Symbol('BreadcrumbPopupContainerFactory');
|
||||
|
||||
export type BreadcrumbID = string;
|
||||
export const BreadcrumbID = Symbol('BreadcrumbID');
|
||||
|
||||
/**
|
||||
* This class creates a popup container at the given position
|
||||
* so that contributions can attach their HTML elements
|
||||
* as children of `BreadcrumbPopupContainer#container`.
|
||||
*
|
||||
* - `dispose()` is called on blur or on hit on escape
|
||||
*/
|
||||
@injectable()
|
||||
export class BreadcrumbPopupContainer implements Disposable {
|
||||
@inject(RendererHost) protected readonly parent: RendererHost;
|
||||
@inject(BreadcrumbID) public readonly breadcrumbId: BreadcrumbID;
|
||||
@inject(Coordinate) protected readonly position: Coordinate;
|
||||
|
||||
protected onDidDisposeEmitter = new Emitter<void>();
|
||||
protected toDispose: DisposableCollection = new DisposableCollection(this.onDidDisposeEmitter);
|
||||
get onDidDispose(): Event<void> {
|
||||
return this.onDidDisposeEmitter.event;
|
||||
}
|
||||
|
||||
protected _container: HTMLElement;
|
||||
get container(): HTMLElement {
|
||||
return this._container;
|
||||
}
|
||||
|
||||
protected _isOpen: boolean;
|
||||
get isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this._container = this.createPopupDiv(this.position);
|
||||
document.addEventListener('keyup', this.escFunction);
|
||||
this._container.focus();
|
||||
this._isOpen = true;
|
||||
}
|
||||
|
||||
protected createPopupDiv(position: Coordinate): HTMLDivElement {
|
||||
const result = window.document.createElement('div');
|
||||
result.className = Styles.BREADCRUMB_POPUP;
|
||||
result.style.left = `${position.x}px`;
|
||||
result.style.top = `${position.y}px`;
|
||||
result.tabIndex = 0;
|
||||
result.addEventListener('focusout', this.onFocusOut);
|
||||
this.parent.appendChild(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected onFocusOut = (event: FocusEvent) => {
|
||||
if (!(event.relatedTarget instanceof Element) || !this._container.contains(event.relatedTarget)) {
|
||||
this.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
protected escFunction = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' || event.key === 'Esc') {
|
||||
this.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
dispose(): void {
|
||||
if (!this.toDispose.disposed) {
|
||||
this.onDidDisposeEmitter.fire();
|
||||
this.toDispose.dispose();
|
||||
this._container.remove();
|
||||
this._isOpen = false;
|
||||
document.removeEventListener('keyup', this.escFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from 'react';
|
||||
import { injectable } from 'inversify';
|
||||
import { Breadcrumb, Styles } from './breadcrumbs-constants';
|
||||
|
||||
export const BreadcrumbRenderer = Symbol('BreadcrumbRenderer');
|
||||
export interface BreadcrumbRenderer {
|
||||
/**
|
||||
* Renders the given breadcrumb. If `onClick` is given, it is called on breadcrumb click.
|
||||
*/
|
||||
render(breadcrumb: Breadcrumb, onMouseDown?: (breadcrumb: Breadcrumb, event: React.MouseEvent) => void): React.ReactNode;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DefaultBreadcrumbRenderer implements BreadcrumbRenderer {
|
||||
render(breadcrumb: Breadcrumb, onMouseDown?: (breadcrumb: Breadcrumb, event: React.MouseEvent) => void): React.ReactNode {
|
||||
return <li key={breadcrumb.id} title={breadcrumb.longLabel}
|
||||
className={Styles.BREADCRUMB_ITEM + (!onMouseDown ? '' : ' ' + Styles.BREADCRUMB_ITEM_HAS_POPUP)}
|
||||
onMouseDown={event => onMouseDown && onMouseDown(breadcrumb, event)}
|
||||
tabIndex={0}
|
||||
data-breadcrumb-id={breadcrumb.id}
|
||||
>
|
||||
{breadcrumb.iconClass && <span className={breadcrumb.iconClass}></span>} <span> {breadcrumb.label}</span>
|
||||
</li >;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { MaybePromise, Event } from '../../common';
|
||||
import { Disposable } from '../../../shared/vscode-languageserver-protocol';
|
||||
import URI from '../../common/uri';
|
||||
|
||||
export namespace Styles {
|
||||
export const BREADCRUMBS = 'theia-breadcrumbs';
|
||||
export const BREADCRUMB_ITEM = 'theia-breadcrumb-item';
|
||||
export const BREADCRUMB_POPUP_OVERLAY_CONTAINER = 'theia-breadcrumbs-popups-overlay';
|
||||
export const BREADCRUMB_POPUP = 'theia-breadcrumbs-popup';
|
||||
export const BREADCRUMB_ITEM_HAS_POPUP = 'theia-breadcrumb-item-haspopup';
|
||||
}
|
||||
|
||||
/** A single breadcrumb in the breadcrumbs bar. */
|
||||
export interface Breadcrumb {
|
||||
|
||||
/** An ID of this breadcrumb that should be unique in the breadcrumbs bar. */
|
||||
readonly id: string;
|
||||
|
||||
/** The breadcrumb type. Should be the same as the contribution type `BreadcrumbsContribution#type`. */
|
||||
readonly type: symbol;
|
||||
|
||||
/** The text that will be rendered as label. */
|
||||
readonly label: string;
|
||||
|
||||
/** A longer text that will be used as tooltip text. */
|
||||
readonly longLabel: string;
|
||||
|
||||
/** A CSS class for the icon. */
|
||||
readonly iconClass?: string;
|
||||
|
||||
/** CSS classes for the container. */
|
||||
readonly containerClass?: string;
|
||||
}
|
||||
|
||||
export const BreadcrumbsContribution = Symbol('BreadcrumbsContribution');
|
||||
export interface BreadcrumbsContribution {
|
||||
|
||||
/**
|
||||
* The breadcrumb type. Breadcrumbs returned by `#computeBreadcrumbs(uri)` should have this as `Breadcrumb#type`.
|
||||
*/
|
||||
readonly type: symbol;
|
||||
|
||||
/**
|
||||
* The priority of this breadcrumbs contribution. Contributions are rendered left to right in order of ascending priority.
|
||||
*/
|
||||
readonly priority: number;
|
||||
|
||||
/**
|
||||
* An event emitter that should fire when breadcrumbs change for a given URI.
|
||||
*/
|
||||
readonly onDidChangeBreadcrumbs: Event<URI>;
|
||||
|
||||
/**
|
||||
* Computes breadcrumbs for a given URI.
|
||||
*/
|
||||
computeBreadcrumbs(uri: URI): MaybePromise<Breadcrumb[]>;
|
||||
|
||||
/**
|
||||
* Attaches the breadcrumb popup content for the given breadcrumb as child to the given parent.
|
||||
* If it returns a Disposable, it is called when the popup closes.
|
||||
*/
|
||||
attachPopupContent(breadcrumb: Breadcrumb, parent: HTMLElement): Promise<Disposable | undefined>;
|
||||
}
|
||||
185
packages/core/src/browser/breadcrumbs/breadcrumbs-renderer.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from 'react';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { ReactRenderer } from '../widgets';
|
||||
import { BreadcrumbsService } from './breadcrumbs-service';
|
||||
import { BreadcrumbRenderer } from './breadcrumb-renderer';
|
||||
import PerfectScrollbar from 'perfect-scrollbar';
|
||||
import URI from '../../common/uri';
|
||||
import { Emitter, Event } from '../../common';
|
||||
import { BreadcrumbPopupContainer } from './breadcrumb-popup-container';
|
||||
import { CorePreferences } from '../../common/core-preferences';
|
||||
import { Breadcrumb, Styles } from './breadcrumbs-constants';
|
||||
import { LabelProvider } from '../label-provider';
|
||||
|
||||
interface Cancelable {
|
||||
canceled: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BreadcrumbsRenderer extends ReactRenderer {
|
||||
|
||||
@inject(BreadcrumbsService)
|
||||
protected readonly breadcrumbsService: BreadcrumbsService;
|
||||
|
||||
@inject(BreadcrumbRenderer)
|
||||
protected readonly breadcrumbRenderer: BreadcrumbRenderer;
|
||||
|
||||
@inject(CorePreferences)
|
||||
protected readonly corePreferences: CorePreferences;
|
||||
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
protected readonly onDidChangeActiveStateEmitter = new Emitter<boolean>();
|
||||
get onDidChangeActiveState(): Event<boolean> {
|
||||
return this.onDidChangeActiveStateEmitter.event;
|
||||
}
|
||||
|
||||
protected uri: URI | undefined;
|
||||
protected breadcrumbs: Breadcrumb[] = [];
|
||||
protected popup: BreadcrumbPopupContainer | undefined;
|
||||
protected scrollbar: PerfectScrollbar | undefined;
|
||||
|
||||
get active(): boolean {
|
||||
return !!this.breadcrumbs.length;
|
||||
}
|
||||
|
||||
protected get breadCrumbsContainer(): Element | undefined {
|
||||
return this.host.firstElementChild ?? undefined;
|
||||
}
|
||||
|
||||
protected refreshCancellationMarker: Cancelable = { canceled: true };
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.onDidChangeActiveStateEmitter);
|
||||
this.toDispose.push(this.breadcrumbsService.onDidChangeBreadcrumbs(uri => {
|
||||
if (this.uri?.isEqual(uri)) {
|
||||
this.refresh(this.uri);
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.corePreferences.onPreferenceChanged(change => {
|
||||
if (change.preferenceName === 'breadcrumbs.enabled') {
|
||||
this.refresh(this.uri);
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.labelProvider.onDidChange(() => this.refresh(this.uri)));
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.toDispose.dispose();
|
||||
if (this.popup) { this.popup.dispose(); }
|
||||
if (this.scrollbar) {
|
||||
this.scrollbar.destroy();
|
||||
this.scrollbar = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(uri?: URI): Promise<void> {
|
||||
this.uri = uri;
|
||||
this.refreshCancellationMarker.canceled = true;
|
||||
const currentCallCanceled = { canceled: false };
|
||||
this.refreshCancellationMarker = currentCallCanceled;
|
||||
let breadcrumbs: Breadcrumb[];
|
||||
if (uri && this.corePreferences['breadcrumbs.enabled']) {
|
||||
breadcrumbs = await this.breadcrumbsService.getBreadcrumbs(uri);
|
||||
} else {
|
||||
breadcrumbs = [];
|
||||
}
|
||||
if (currentCallCanceled.canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wasActive = this.active;
|
||||
this.breadcrumbs = breadcrumbs;
|
||||
const isActive = this.active;
|
||||
if (wasActive !== isActive) {
|
||||
this.onDidChangeActiveStateEmitter.fire(isActive);
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected update(): void {
|
||||
this.render();
|
||||
|
||||
if (!this.scrollbar) {
|
||||
this.createScrollbar();
|
||||
} else {
|
||||
this.scrollbar.update();
|
||||
}
|
||||
this.scrollToEnd();
|
||||
}
|
||||
|
||||
protected createScrollbar(): void {
|
||||
const { breadCrumbsContainer } = this;
|
||||
if (breadCrumbsContainer) {
|
||||
this.scrollbar = new PerfectScrollbar(breadCrumbsContainer, {
|
||||
handlers: ['drag-thumb', 'keyboard', 'wheel', 'touch'],
|
||||
useBothWheelAxes: true,
|
||||
scrollXMarginOffset: 4,
|
||||
suppressScrollY: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected scrollToEnd(): void {
|
||||
const { breadCrumbsContainer } = this;
|
||||
if (breadCrumbsContainer) {
|
||||
breadCrumbsContainer.scrollLeft = breadCrumbsContainer.scrollWidth;
|
||||
}
|
||||
}
|
||||
|
||||
protected override doRender(): React.ReactNode {
|
||||
return <ul className={Styles.BREADCRUMBS}>{this.renderBreadcrumbs()}</ul>;
|
||||
}
|
||||
|
||||
protected renderBreadcrumbs(): React.ReactNode {
|
||||
return this.breadcrumbs.map(breadcrumb => this.breadcrumbRenderer.render(breadcrumb, this.togglePopup));
|
||||
}
|
||||
|
||||
protected togglePopup = (breadcrumb: Breadcrumb, event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let openPopup = true;
|
||||
if (this.popup?.isOpen) {
|
||||
this.popup.dispose();
|
||||
|
||||
// There is a popup open. If the popup is the popup that belongs to the currently clicked breadcrumb
|
||||
// just close the popup. If another breadcrumb was clicked, open the new popup immediately.
|
||||
openPopup = this.popup.breadcrumbId !== breadcrumb.id;
|
||||
} else {
|
||||
this.popup = undefined;
|
||||
}
|
||||
if (openPopup) {
|
||||
const { currentTarget } = event;
|
||||
const breadcrumbElement = currentTarget.closest(`.${Styles.BREADCRUMB_ITEM}`);
|
||||
if (breadcrumbElement) {
|
||||
const { left: x, bottom: y } = breadcrumbElement.getBoundingClientRect();
|
||||
this.breadcrumbsService.openPopup(breadcrumb, { x, y }).then(popup => { this.popup = popup; });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const BreadcrumbsRendererFactory = Symbol('BreadcrumbsRendererFactory');
|
||||
export interface BreadcrumbsRendererFactory {
|
||||
(): BreadcrumbsRenderer;
|
||||
}
|
||||
108
packages/core/src/browser/breadcrumbs/breadcrumbs-service.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, named, postConstruct } from 'inversify';
|
||||
import { ContributionProvider, Prioritizeable, Emitter, Event } from '../../common';
|
||||
import URI from '../../common/uri';
|
||||
import { Coordinate } from '../context-menu-renderer';
|
||||
import { BreadcrumbPopupContainer, BreadcrumbPopupContainerFactory } from './breadcrumb-popup-container';
|
||||
import { BreadcrumbsContribution, Styles, Breadcrumb } from './breadcrumbs-constants';
|
||||
|
||||
@injectable()
|
||||
export class BreadcrumbsService {
|
||||
|
||||
@inject(ContributionProvider) @named(BreadcrumbsContribution)
|
||||
protected readonly contributions: ContributionProvider<BreadcrumbsContribution>;
|
||||
|
||||
@inject(BreadcrumbPopupContainerFactory) protected readonly breadcrumbPopupContainerFactory: BreadcrumbPopupContainerFactory;
|
||||
|
||||
protected hasSubscribed = false;
|
||||
|
||||
protected popupsOverlayContainer: HTMLDivElement;
|
||||
|
||||
protected readonly onDidChangeBreadcrumbsEmitter = new Emitter<URI>();
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.createOverlayContainer();
|
||||
}
|
||||
|
||||
protected createOverlayContainer(): void {
|
||||
this.popupsOverlayContainer = window.document.createElement('div');
|
||||
this.popupsOverlayContainer.id = Styles.BREADCRUMB_POPUP_OVERLAY_CONTAINER;
|
||||
if (window.document.body) {
|
||||
window.document.body.appendChild(this.popupsOverlayContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to this event emitter to be notified when the breadcrumbs have changed.
|
||||
* The URI is the URI of the editor the breadcrumbs have changed for.
|
||||
*/
|
||||
get onDidChangeBreadcrumbs(): Event<URI> {
|
||||
// This lazy subscription is to address problems in inversify's instantiation routine
|
||||
// related to use of the IconThemeService by different components instantiated by the
|
||||
// ContributionProvider.
|
||||
if (!this.hasSubscribed) {
|
||||
this.subscribeToContributions();
|
||||
}
|
||||
return this.onDidChangeBreadcrumbsEmitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to the onDidChangeBreadcrumbs events for all contributions.
|
||||
*/
|
||||
protected subscribeToContributions(): void {
|
||||
this.hasSubscribed = true;
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
contribution.onDidChangeBreadcrumbs(uri => this.onDidChangeBreadcrumbsEmitter.fire(uri));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumbs for a given URI, possibly an empty list.
|
||||
*/
|
||||
async getBreadcrumbs(uri: URI): Promise<Breadcrumb[]> {
|
||||
const result: Breadcrumb[] = [];
|
||||
for (const contribution of await this.prioritizedContributions()) {
|
||||
result.push(...await contribution.computeBreadcrumbs(uri));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async prioritizedContributions(): Promise<BreadcrumbsContribution[]> {
|
||||
const prioritized = await Prioritizeable.prioritizeAll(
|
||||
this.contributions.getContributions(), contribution => contribution.priority);
|
||||
return prioritized.map(p => p.value).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a popup for the given breadcrumb at the given position.
|
||||
*/
|
||||
async openPopup(breadcrumb: Breadcrumb, position: Coordinate): Promise<BreadcrumbPopupContainer | undefined> {
|
||||
const contribution = this.contributions.getContributions().find(c => c.type === breadcrumb.type);
|
||||
if (contribution) {
|
||||
const popup = this.breadcrumbPopupContainerFactory(this.popupsOverlayContainer, breadcrumb.id, position);
|
||||
const popupContent = await contribution.attachPopupContent(breadcrumb, popup.container);
|
||||
if (popupContent && popup.isOpen) {
|
||||
popup.onDidDispose(() => popupContent.dispose());
|
||||
} else {
|
||||
popupContent?.dispose();
|
||||
}
|
||||
return popup;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
packages/core/src/browser/breadcrumbs/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './breadcrumb-popup-container';
|
||||
export * from './breadcrumb-renderer';
|
||||
export * from './breadcrumbs-renderer';
|
||||
export * from './breadcrumbs-service';
|
||||
export * from './breadcrumbs-constants';
|
||||
122
packages/core/src/browser/browser-clipboard-service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { isFirefox } from './browser';
|
||||
import { ClipboardService } from './clipboard-service';
|
||||
import { ILogger } from '../common/logger';
|
||||
import { MessageService } from '../common/message-service';
|
||||
import { nls } from '../common/nls';
|
||||
|
||||
export interface NavigatorClipboard {
|
||||
readText(): Promise<string>;
|
||||
writeText(value: string): Promise<void>;
|
||||
}
|
||||
export interface PermissionStatus {
|
||||
state: 'granted' | 'prompt' | 'denied'
|
||||
}
|
||||
export interface NavigatorPermissions {
|
||||
query(options: { name: string }): Promise<PermissionStatus>
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BrowserClipboardService implements ClipboardService {
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
async readText(): Promise<string> {
|
||||
let permission;
|
||||
try {
|
||||
permission = await this.queryPermission('clipboard-read');
|
||||
} catch (e1) {
|
||||
this.logger.error('Failed checking a clipboard-read permission.', e1);
|
||||
// in FireFox, Clipboard API isn't gated with the permissions
|
||||
try {
|
||||
return await this.getClipboardAPI().readText();
|
||||
} catch (e2) {
|
||||
this.logger.error('Failed reading clipboard content.', e2);
|
||||
if (isFirefox) {
|
||||
this.messageService.warn(nls.localize(
|
||||
'theia/navigator/clipboardWarnFirefox',
|
||||
// eslint-disable-next-line max-len
|
||||
"Clipboard API is not available. It can be enabled by '{0}' preference on '{1}' page. Then reload Theia. Note, it will allow FireFox getting full access to the system clipboard.", 'dom.events.testing.asyncClipboard', 'about:config'
|
||||
));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if (permission.state === 'denied') {
|
||||
// most likely, the user intentionally denied the access
|
||||
this.messageService.warn(nls.localize(
|
||||
'theia/navigator/clipboardWarn',
|
||||
"Access to the clipboard is denied. Check your browser's permission."
|
||||
));
|
||||
return '';
|
||||
}
|
||||
return this.getClipboardAPI().readText();
|
||||
}
|
||||
|
||||
async writeText(value: string): Promise<void> {
|
||||
let permission;
|
||||
try {
|
||||
permission = await this.queryPermission('clipboard-write');
|
||||
} catch (e1) {
|
||||
this.logger.error('Failed checking a clipboard-write permission.', e1);
|
||||
// in FireFox, Clipboard API isn't gated with the permissions
|
||||
try {
|
||||
await this.getClipboardAPI().writeText(value);
|
||||
return;
|
||||
} catch (e2) {
|
||||
this.logger.error('Failed writing to the clipboard.', e2);
|
||||
if (isFirefox) {
|
||||
this.messageService.warn(nls.localize(
|
||||
'theia/core/navigator/clipboardWarnFirefox',
|
||||
// eslint-disable-next-line max-len
|
||||
"Clipboard API is not available. It can be enabled by '{0}' preference on '{1}' page. Then reload Theia. Note, it will allow FireFox getting full access to the system clipboard.", 'dom.events.testing.asyncClipboard', 'about:config'
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (permission.state === 'denied') {
|
||||
// most likely, the user intentionally denied the access
|
||||
this.messageService.warn(nls.localize(
|
||||
'theia/core/navigator/clipboardWarn',
|
||||
"Access to the clipboard is denied. Check your browser's permission."
|
||||
));
|
||||
return;
|
||||
}
|
||||
return this.getClipboardAPI().writeText(value);
|
||||
}
|
||||
|
||||
protected async queryPermission(name: string): Promise<PermissionStatus> {
|
||||
if ('permissions' in navigator) {
|
||||
return (<NavigatorPermissions>navigator['permissions']).query({ name: name });
|
||||
}
|
||||
throw new Error('Permissions API unavailable');
|
||||
}
|
||||
|
||||
protected getClipboardAPI(): NavigatorClipboard {
|
||||
if ('clipboard' in navigator) {
|
||||
return (<NavigatorClipboard>navigator['clipboard']);
|
||||
}
|
||||
throw new Error('Async Clipboard API unavailable');
|
||||
}
|
||||
}
|
||||
244
packages/core/src/browser/browser.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, environment, isOSX } from '../common';
|
||||
|
||||
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
||||
|
||||
export const isIE = (userAgent.indexOf('Trident') >= 0);
|
||||
export const isEdge = (userAgent.indexOf('Edge/') >= 0);
|
||||
export const isEdgeOrIE = isIE || isEdge;
|
||||
|
||||
export const isOpera = (userAgent.indexOf('Opera') >= 0);
|
||||
export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
|
||||
export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
|
||||
export const isChrome = (userAgent.indexOf('Chrome') >= 0);
|
||||
export const isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.indexOf('Safari') >= 0);
|
||||
export const isIPad = (userAgent.indexOf('iPad') >= 0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
/**
|
||||
* @deprecated use Environment.electron.is
|
||||
*/
|
||||
export const isNative = environment.electron.is();
|
||||
/**
|
||||
* Determines whether the backend is running in a remote environment.
|
||||
* I.e. we use the browser version or connect to a remote Theia instance in Electron.
|
||||
*/
|
||||
export const isRemote = !environment.electron.is() || new URL(location.href).searchParams.has('localPort');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isBasicWasmSupported = typeof (window as any).WebAssembly !== 'undefined';
|
||||
|
||||
/**
|
||||
* Resolves after the next animation frame if no parameter is given,
|
||||
* or after the given number of animation frames.
|
||||
*/
|
||||
export function animationFrame(n: number = 1): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
function frameFunc(): void {
|
||||
if (n <= 0) {
|
||||
resolve();
|
||||
} else {
|
||||
n--;
|
||||
requestAnimationFrame(frameFunc);
|
||||
}
|
||||
}
|
||||
frameFunc();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a magnitude value (e.g. width, height, left, top) from a CSS attribute value.
|
||||
* Returns the given default value (or undefined) if the value cannot be determined,
|
||||
* e.g. because it is a relative value like `50%` or `auto`.
|
||||
*/
|
||||
export function parseCssMagnitude(value: string | null, defaultValue: number): number;
|
||||
export function parseCssMagnitude(value: string | null, defaultValue?: number): number | undefined {
|
||||
if (value) {
|
||||
let parsed: number;
|
||||
if (value.endsWith('px')) {
|
||||
parsed = parseFloat(value.substring(0, value.length - 2));
|
||||
} else {
|
||||
parsed = parseFloat(value);
|
||||
}
|
||||
if (!isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the number of milliseconds from a CSS time value.
|
||||
* Returns the given default value (or undefined) if the value cannot be determined.
|
||||
*/
|
||||
export function parseCssTime(time: string | null, defaultValue: number): number;
|
||||
export function parseCssTime(time: string | null, defaultValue?: number): number | undefined {
|
||||
if (time) {
|
||||
let parsed: number;
|
||||
if (time.endsWith('ms')) {
|
||||
parsed = parseFloat(time.substring(0, time.length - 2));
|
||||
} else if (time.endsWith('s')) {
|
||||
parsed = parseFloat(time.substring(0, time.length - 1)) * 1000;
|
||||
} else {
|
||||
parsed = parseFloat(time);
|
||||
}
|
||||
if (!isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
interface ElementScroll {
|
||||
left: number
|
||||
top: number
|
||||
maxLeft: number
|
||||
maxTop: number
|
||||
}
|
||||
|
||||
function getMonacoEditorScroll(elem: HTMLElement): ElementScroll | undefined {
|
||||
const linesContent = elem.querySelector('.lines-content') as HTMLElement;
|
||||
const viewLines = elem.querySelector('.view-lines') as HTMLElement;
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
if (linesContent === null || viewLines === null) {
|
||||
return undefined;
|
||||
}
|
||||
const linesContentStyle = linesContent.style;
|
||||
const elemStyle = elem.style;
|
||||
const viewLinesStyle = viewLines.style;
|
||||
return {
|
||||
left: -parseCssMagnitude(linesContentStyle.left, 0),
|
||||
top: -parseCssMagnitude(linesContentStyle.top, 0),
|
||||
maxLeft: parseCssMagnitude(viewLinesStyle.width, 0) - parseCssMagnitude(elemStyle.width, 0),
|
||||
maxTop: parseCssMagnitude(viewLinesStyle.height, 0) - parseCssMagnitude(elemStyle.height, 0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent browser back/forward navigation of a mouse wheel event.
|
||||
*/
|
||||
export function preventNavigation(event: WheelEvent): void {
|
||||
const { currentTarget, deltaX, deltaY } = event;
|
||||
const absDeltaX = Math.abs(deltaX);
|
||||
const absDeltaY = Math.abs(deltaY);
|
||||
let elem = event.target as Element | null;
|
||||
|
||||
while (elem && elem !== currentTarget) {
|
||||
let scroll: ElementScroll | undefined;
|
||||
if (elem.classList.contains('monaco-scrollable-element')) {
|
||||
scroll = getMonacoEditorScroll(elem as HTMLElement);
|
||||
} else {
|
||||
scroll = {
|
||||
left: elem.scrollLeft,
|
||||
top: elem.scrollTop,
|
||||
maxLeft: elem.scrollWidth - elem.clientWidth,
|
||||
maxTop: elem.scrollHeight - elem.clientHeight
|
||||
};
|
||||
}
|
||||
if (scroll) {
|
||||
const scrollH = scroll.maxLeft > 0 && (deltaX < 0 && scroll.left > 0 || deltaX > 0 && scroll.left < scroll.maxLeft);
|
||||
const scrollV = scroll.maxTop > 0 && (deltaY < 0 && scroll.top > 0 || deltaY > 0 && scroll.top < scroll.maxTop);
|
||||
if (scrollH && scrollV || scrollH && absDeltaX > absDeltaY || scrollV && absDeltaY > absDeltaX) {
|
||||
// The event is consumed by the scrollable child element
|
||||
return;
|
||||
}
|
||||
}
|
||||
elem = elem.parentElement;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
export type PartialCSSStyle = Omit<Partial<CSSStyleDeclaration>,
|
||||
'visibility' |
|
||||
'display' |
|
||||
'parentRule' |
|
||||
'getPropertyPriority' |
|
||||
'getPropertyValue' |
|
||||
'item' |
|
||||
'removeProperty' |
|
||||
'setProperty'>;
|
||||
|
||||
export function measureTextWidth(text: string | string[], style?: PartialCSSStyle): number {
|
||||
const measureElement = getMeasurementElement(style);
|
||||
text = Array.isArray(text) ? text : [text];
|
||||
let width = 0;
|
||||
for (const item of text) {
|
||||
measureElement.textContent = item;
|
||||
width = Math.max(measureElement.getBoundingClientRect().width, width);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
export function measureTextHeight(text: string | string[], style?: PartialCSSStyle): number {
|
||||
const measureElement = getMeasurementElement(style);
|
||||
text = Array.isArray(text) ? text : [text];
|
||||
let height = 0;
|
||||
for (const item of text) {
|
||||
measureElement.textContent = item;
|
||||
height = Math.max(measureElement.getBoundingClientRect().height, height);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
const defaultStyle = document.createElement('div').style;
|
||||
defaultStyle.fontFamily = 'var(--theia-ui-font-family)';
|
||||
defaultStyle.fontSize = 'var(--theia-ui-font-size1)';
|
||||
defaultStyle.visibility = 'hidden';
|
||||
|
||||
function getMeasurementElement(style?: PartialCSSStyle): HTMLElement {
|
||||
let measureElement = document.getElementById('measure');
|
||||
if (!measureElement) {
|
||||
measureElement = document.createElement('span');
|
||||
measureElement.id = 'measure';
|
||||
measureElement.style.fontFamily = defaultStyle.fontFamily;
|
||||
measureElement.style.fontSize = defaultStyle.fontSize;
|
||||
measureElement.style.visibility = defaultStyle.visibility;
|
||||
document.body.appendChild(measureElement);
|
||||
}
|
||||
const measureStyle = measureElement.style;
|
||||
// Reset styling first
|
||||
for (let i = 0; i < measureStyle.length; i++) {
|
||||
const property = measureStyle[i];
|
||||
measureStyle.setProperty(property, defaultStyle.getPropertyValue(property));
|
||||
}
|
||||
// Apply new styling
|
||||
if (style) {
|
||||
for (const [key, value] of Object.entries(style)) {
|
||||
measureStyle.setProperty(key, value as string);
|
||||
}
|
||||
}
|
||||
return measureElement;
|
||||
}
|
||||
|
||||
export function onDomEvent<K extends keyof HTMLElementEventMap>(
|
||||
element: Node,
|
||||
type: K,
|
||||
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown,
|
||||
options?: boolean | AddEventListenerOptions): Disposable {
|
||||
element.addEventListener(type, listener, options);
|
||||
return { dispose: () => element.removeEventListener(type, listener, options) };
|
||||
}
|
||||
|
||||
/** Is a mouse `event` the pointer event that triggers the context menu on this platform? */
|
||||
export function isContextMenuEvent(event: MouseEvent): boolean {
|
||||
return isOSX && event.ctrlKey && event.button === 0 || event.button === 2;
|
||||
}
|
||||
23
packages/core/src/browser/clipboard-service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { MaybePromise } from '../common/types';
|
||||
|
||||
export const ClipboardService = Symbol('ClipboardService');
|
||||
export interface ClipboardService {
|
||||
readText(): MaybePromise<string>;
|
||||
writeText(value: string): MaybePromise<void>;
|
||||
}
|
||||
110
packages/core/src/browser/color-application-contribution.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, named } from 'inversify';
|
||||
import { ColorRegistry } from './color-registry';
|
||||
import { Emitter } from '../common/event';
|
||||
import { ThemeService } from './theming';
|
||||
import { FrontendApplicationContribution } from './frontend-application-contribution';
|
||||
import { ContributionProvider } from '../common/contribution-provider';
|
||||
import { Disposable, DisposableCollection } from '../common/disposable';
|
||||
import { DEFAULT_BACKGROUND_COLOR_STORAGE_KEY } from './frontend-application-config-provider';
|
||||
import { SecondaryWindowHandler } from './secondary-window-handler';
|
||||
|
||||
export const ColorContribution = Symbol('ColorContribution');
|
||||
export interface ColorContribution {
|
||||
registerColors(colors: ColorRegistry): void;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ColorApplicationContribution implements FrontendApplicationContribution {
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<void>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
private readonly windows: Set<Window> = new Set();
|
||||
|
||||
@inject(ColorRegistry)
|
||||
protected readonly colors: ColorRegistry;
|
||||
|
||||
@inject(ContributionProvider) @named(ColorContribution)
|
||||
protected readonly colorContributions: ContributionProvider<ColorContribution>;
|
||||
|
||||
@inject(ThemeService) protected readonly themeService: ThemeService;
|
||||
|
||||
@inject(SecondaryWindowHandler)
|
||||
protected readonly secondaryWindowHandler: SecondaryWindowHandler;
|
||||
|
||||
onStart(): void {
|
||||
for (const contribution of this.colorContributions.getContributions()) {
|
||||
contribution.registerColors(this.colors);
|
||||
}
|
||||
this.themeService.initialized.then(() => this.update());
|
||||
this.themeService.onDidColorThemeChange(() => {
|
||||
this.update();
|
||||
this.updateThemeBackground();
|
||||
});
|
||||
this.colors.onDidChange(() => this.update());
|
||||
|
||||
this.registerWindow(window);
|
||||
this.secondaryWindowHandler.onWillAddWidget(([widget, window]) => {
|
||||
this.registerWindow(window);
|
||||
});
|
||||
this.secondaryWindowHandler.onWillRemoveWidget(([widget, window]) => {
|
||||
this.windows.delete(window);
|
||||
});
|
||||
}
|
||||
|
||||
registerWindow(win: Window): void {
|
||||
this.windows.add(win);
|
||||
this.updateWindow(win);
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
|
||||
protected readonly toUpdate = new DisposableCollection();
|
||||
protected update(): void {
|
||||
this.toUpdate.dispose();
|
||||
this.windows.forEach(win => this.updateWindow(win));
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
|
||||
protected updateWindow(win: Window): void {
|
||||
const theme = 'theia-' + this.themeService.getCurrentTheme().type;
|
||||
|
||||
win.document.body.classList.add(theme);
|
||||
this.toUpdate.push(Disposable.create(() => win.document.body.classList.remove(theme)));
|
||||
|
||||
const documentElement = win.document.documentElement;
|
||||
if (documentElement) {
|
||||
for (const id of this.colors.getColors()) {
|
||||
const variable = this.colors.getCurrentCssVariable(id);
|
||||
if (variable) {
|
||||
const { name, value } = variable;
|
||||
documentElement.style.setProperty(name, value);
|
||||
this.toUpdate.push(Disposable.create(() => documentElement.style.removeProperty(name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateThemeBackground(): void {
|
||||
const color = this.colors.getCurrentColor('editor.background');
|
||||
if (color) {
|
||||
window.localStorage.setItem(DEFAULT_BACKGROUND_COLOR_STORAGE_KEY, color);
|
||||
} else {
|
||||
window.localStorage.removeItem(DEFAULT_BACKGROUND_COLOR_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
packages/core/src/browser/color-registry.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from 'inversify';
|
||||
import { DisposableCollection, Disposable } from '../common/disposable';
|
||||
import { Emitter } from '../common/event';
|
||||
import { ColorDefinition, ColorCssVariable } from '../common/color';
|
||||
|
||||
@injectable()
|
||||
export class ColorRegistry {
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<void>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
protected fireDidChange(): void {
|
||||
this.onDidChangeEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
*getColors(): IterableIterator<string> { }
|
||||
|
||||
getCurrentCssVariable(id: string): ColorCssVariable | undefined {
|
||||
const value = this.getCurrentColor(id);
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const name = this.toCssVariableName(id);
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
toCssVariableName(id: string, prefix = 'theia'): string {
|
||||
return `--${prefix}-${id.replace(/\./g, '-')}`;
|
||||
}
|
||||
|
||||
getCurrentColor(id: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
register(...definitions: ColorDefinition[]): Disposable {
|
||||
const result = new DisposableCollection(...definitions.map(definition => this.doRegister(definition)));
|
||||
this.fireDidChange();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected doRegister(definition: ColorDefinition): Disposable {
|
||||
return Disposable.NULL;
|
||||
}
|
||||
|
||||
}
|
||||
54
packages/core/src/browser/command-open-handler.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { CommandService } from '../common/command';
|
||||
import URI from '../common/uri';
|
||||
import { OpenHandler } from './opener-service';
|
||||
|
||||
@injectable()
|
||||
export class CommandOpenHandler implements OpenHandler {
|
||||
|
||||
readonly id = 'command';
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commands: CommandService;
|
||||
|
||||
canHandle(uri: URI): number {
|
||||
return uri.scheme === 'command' ? 500 : -1;
|
||||
}
|
||||
|
||||
async open(uri: URI): Promise<boolean> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let args: any = [];
|
||||
try {
|
||||
args = JSON.parse(decodeURIComponent(uri.query));
|
||||
} catch {
|
||||
// ignore and retry
|
||||
try {
|
||||
args = JSON.parse(uri.query);
|
||||
} catch {
|
||||
args = uri.query;
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(args)) {
|
||||
args = [args];
|
||||
}
|
||||
await this.commands.executeCommand(uri.path.toString(), ...args);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
281
packages/core/src/browser/common-commands.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Command } from '../common/command';
|
||||
import { nls } from '../common/nls';
|
||||
|
||||
export namespace CommonCommands {
|
||||
|
||||
export const FILE_CATEGORY = 'File';
|
||||
export const VIEW_CATEGORY = 'View';
|
||||
export const CREATE_CATEGORY = 'Create';
|
||||
export const PREFERENCES_CATEGORY = 'Preferences';
|
||||
export const MANAGE_CATEGORY = 'Manage';
|
||||
export const FILE_CATEGORY_KEY = nls.getDefaultKey(FILE_CATEGORY);
|
||||
export const VIEW_CATEGORY_KEY = nls.getDefaultKey(VIEW_CATEGORY);
|
||||
export const PREFERENCES_CATEGORY_KEY = nls.getDefaultKey(PREFERENCES_CATEGORY);
|
||||
|
||||
export const OPEN: Command = {
|
||||
id: 'core.open',
|
||||
};
|
||||
|
||||
export const CUT = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.cut',
|
||||
label: 'Cut'
|
||||
});
|
||||
export const COPY = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.copy',
|
||||
label: 'Copy'
|
||||
});
|
||||
export const PASTE = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.paste',
|
||||
label: 'Paste'
|
||||
});
|
||||
|
||||
export const COPY_PATH = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.copy.path',
|
||||
label: 'Copy Path'
|
||||
});
|
||||
|
||||
export const UNDO = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.undo',
|
||||
label: 'Undo'
|
||||
});
|
||||
export const REDO = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.redo',
|
||||
label: 'Redo'
|
||||
});
|
||||
export const SELECT_ALL = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.selectAll',
|
||||
label: 'Select All'
|
||||
});
|
||||
|
||||
export const FIND = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.find',
|
||||
label: 'Find'
|
||||
});
|
||||
export const REPLACE = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.replace',
|
||||
label: 'Replace'
|
||||
});
|
||||
|
||||
export const NEXT_TAB = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.nextTab',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Show Next Tab'
|
||||
});
|
||||
export const PREVIOUS_TAB = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.previousTab',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Show Previous Tab'
|
||||
});
|
||||
export const NEXT_TAB_IN_GROUP = Command.toLocalizedCommand({
|
||||
id: 'core.nextTabInGroup',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Switch to Next Tab in Group'
|
||||
}, 'theia/core/common/showNextTabInGroup', VIEW_CATEGORY_KEY);
|
||||
export const PREVIOUS_TAB_IN_GROUP = Command.toLocalizedCommand({
|
||||
id: 'core.previousTabInGroup',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Switch to Previous Tab in Group'
|
||||
}, 'theia/core/common/showPreviousTabInGroup', VIEW_CATEGORY_KEY);
|
||||
export const NEXT_TAB_GROUP = Command.toLocalizedCommand({
|
||||
id: 'core.nextTabGroup',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Switch to Next Tab Group'
|
||||
}, 'theia/core/common/showNextTabGroup', VIEW_CATEGORY_KEY);
|
||||
export const PREVIOUS_TAB_GROUP = Command.toLocalizedCommand({
|
||||
id: 'core.previousTabBar',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Switch to Previous Tab Group'
|
||||
}, 'theia/core/common/showPreviousTabGroup', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_TAB = Command.toLocalizedCommand({
|
||||
id: 'core.close.tab',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Tab'
|
||||
}, 'theia/core/common/closeTab', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_OTHER_TABS = Command.toLocalizedCommand({
|
||||
id: 'core.close.other.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Other Tabs'
|
||||
}, 'theia/core/common/closeOthers', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_SAVED_TABS = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.closeUnmodifiedEditors',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Saved Editors in Group',
|
||||
});
|
||||
export const CLOSE_RIGHT_TABS = Command.toLocalizedCommand({
|
||||
id: 'core.close.right.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Tabs to the Right'
|
||||
}, 'theia/core/common/closeRight', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_ALL_TABS = Command.toLocalizedCommand({
|
||||
id: 'core.close.all.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close All Tabs'
|
||||
}, 'theia/core/common/closeAll', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_MAIN_TAB = Command.toLocalizedCommand({
|
||||
id: 'core.close.main.tab',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Tab in Main Area'
|
||||
}, 'theia/core/common/closeTabMain', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_OTHER_MAIN_TABS = Command.toLocalizedCommand({
|
||||
id: 'core.close.other.main.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close Other Tabs in Main Area'
|
||||
}, 'theia/core/common/closeOtherTabMain', VIEW_CATEGORY_KEY);
|
||||
export const CLOSE_ALL_MAIN_TABS = Command.toLocalizedCommand({
|
||||
id: 'core.close.all.main.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Close All Tabs in Main Area'
|
||||
}, 'theia/core/common/closeAllTabMain', VIEW_CATEGORY_KEY);
|
||||
export const COLLAPSE_PANEL = Command.toLocalizedCommand({
|
||||
id: 'core.collapse.tab',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Collapse Side Panel'
|
||||
}, 'theia/core/common/collapseTab', VIEW_CATEGORY_KEY);
|
||||
export const COLLAPSE_ALL_PANELS = Command.toLocalizedCommand({
|
||||
id: 'core.collapse.all.tabs',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Collapse All Side Panels'
|
||||
}, 'theia/core/common/collapseAllTabs', VIEW_CATEGORY_KEY);
|
||||
export const TOGGLE_BOTTOM_PANEL = Command.toLocalizedCommand({
|
||||
id: 'core.toggle.bottom.panel',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Bottom Panel'
|
||||
}, 'theia/core/common/collapseBottomPanel', VIEW_CATEGORY_KEY);
|
||||
export const TOGGLE_LEFT_PANEL = Command.toLocalizedCommand({
|
||||
id: 'core.toggle.left.panel',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Left Panel'
|
||||
}, 'theia/core/common/collapseLeftPanel', VIEW_CATEGORY_KEY);
|
||||
export const TOGGLE_RIGHT_PANEL = Command.toLocalizedCommand({
|
||||
id: 'core.toggle.right.panel',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Right Panel'
|
||||
}, 'theia/core/common/collapseRightPanel', VIEW_CATEGORY_KEY);
|
||||
export const TOGGLE_STATUS_BAR = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.toggleStatusbarVisibility',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Status Bar Visibility'
|
||||
});
|
||||
export const PIN_TAB = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.pinEditor',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Pin Editor'
|
||||
});
|
||||
export const UNPIN_TAB = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.unpinEditor',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Unpin Editor'
|
||||
});
|
||||
export const TOGGLE_MAXIMIZED = Command.toLocalizedCommand({
|
||||
id: 'core.toggleMaximized',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Maximized'
|
||||
}, 'theia/core/common/toggleMaximized', VIEW_CATEGORY_KEY);
|
||||
export const OPEN_VIEW = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.openView',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Open View...'
|
||||
});
|
||||
export const SHOW_MENU_BAR = Command.toDefaultLocalizedCommand({
|
||||
id: 'window.menuBarVisibility',
|
||||
category: VIEW_CATEGORY,
|
||||
label: 'Toggle Menu Bar'
|
||||
});
|
||||
/**
|
||||
* Command Parameters:
|
||||
* - `fileName`: string
|
||||
* - `directory`: URI
|
||||
*/
|
||||
export const NEW_FILE = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.files.newFile',
|
||||
category: FILE_CATEGORY
|
||||
});
|
||||
// This command immediately opens a new untitled text file
|
||||
// Some VS Code extensions use this command to create new files
|
||||
export const NEW_UNTITLED_TEXT_FILE = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.files.newUntitledFile',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'New Untitled Text File'
|
||||
});
|
||||
// This command opens a quick pick to select a file type to create
|
||||
export const PICK_NEW_FILE = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.files.pickNewFile',
|
||||
category: CREATE_CATEGORY,
|
||||
label: 'New File...'
|
||||
});
|
||||
export const SAVE = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.save',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'Save',
|
||||
});
|
||||
export const SAVE_AS = Command.toDefaultLocalizedCommand({
|
||||
id: 'file.saveAs',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'Save As...',
|
||||
});
|
||||
export const SAVE_WITHOUT_FORMATTING = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.saveWithoutFormatting',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'Save without Formatting',
|
||||
});
|
||||
export const SAVE_ALL = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.saveAll',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'Save All',
|
||||
});
|
||||
|
||||
export const AUTO_SAVE = Command.toDefaultLocalizedCommand({
|
||||
id: 'textEditor.commands.autosave',
|
||||
category: FILE_CATEGORY,
|
||||
label: 'Auto Save',
|
||||
});
|
||||
|
||||
export const ABOUT_COMMAND = Command.toDefaultLocalizedCommand({
|
||||
id: 'core.about',
|
||||
label: 'About'
|
||||
});
|
||||
|
||||
export const OPEN_PREFERENCES = Command.toDefaultLocalizedCommand({
|
||||
id: 'preferences:open',
|
||||
category: PREFERENCES_CATEGORY,
|
||||
label: 'Open Settings (UI)',
|
||||
});
|
||||
|
||||
export const SELECT_COLOR_THEME = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.selectTheme',
|
||||
label: 'Color Theme',
|
||||
category: PREFERENCES_CATEGORY
|
||||
});
|
||||
export const SELECT_ICON_THEME = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.selectIconTheme',
|
||||
label: 'File Icon Theme',
|
||||
category: PREFERENCES_CATEGORY
|
||||
});
|
||||
|
||||
export const CONFIGURE_DISPLAY_LANGUAGE = Command.toDefaultLocalizedCommand({
|
||||
id: 'workbench.action.configureLanguage',
|
||||
label: 'Configure Display Language'
|
||||
});
|
||||
|
||||
export const TOGGLE_BREADCRUMBS = Command.toDefaultLocalizedCommand({
|
||||
id: 'breadcrumbs.toggle',
|
||||
label: 'Toggle Breadcrumbs',
|
||||
category: VIEW_CATEGORY
|
||||
});
|
||||
}
|
||||
2572
packages/core/src/browser/common-frontend-contribution.ts
Normal file
60
packages/core/src/browser/common-menus.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { MAIN_MENU_BAR, MANAGE_MENU } from '../common/menu';
|
||||
|
||||
export namespace CommonMenus {
|
||||
|
||||
export const FILE = [...MAIN_MENU_BAR, '1_file'];
|
||||
export const FILE_NEW_TEXT = [...FILE, '1_new_text'];
|
||||
export const FILE_NEW = [...FILE, '1_new'];
|
||||
export const FILE_OPEN = [...FILE, '2_open'];
|
||||
export const FILE_SAVE = [...FILE, '3_save'];
|
||||
export const FILE_AUTOSAVE = [...FILE, '4_autosave'];
|
||||
export const FILE_SETTINGS = [...FILE, '5_settings'];
|
||||
export const FILE_SETTINGS_SUBMENU = [...FILE_SETTINGS, '1_settings_submenu'];
|
||||
export const FILE_SETTINGS_SUBMENU_OPEN = [...FILE_SETTINGS_SUBMENU, '1_settings_submenu_open'];
|
||||
export const FILE_SETTINGS_SUBMENU_THEME = [...FILE_SETTINGS_SUBMENU, '2_settings_submenu_theme'];
|
||||
export const FILE_CLOSE = [...FILE, '6_close'];
|
||||
|
||||
export const FILE_NEW_CONTRIBUTIONS = ['file', 'newFile'];
|
||||
|
||||
export const EDIT = [...MAIN_MENU_BAR, '2_edit'];
|
||||
export const EDIT_UNDO = [...EDIT, '1_undo'];
|
||||
export const EDIT_CLIPBOARD = [...EDIT, '2_clipboard'];
|
||||
export const EDIT_FIND = [...EDIT, '3_find'];
|
||||
|
||||
export const VIEW = [...MAIN_MENU_BAR, '4_view'];
|
||||
export const VIEW_PRIMARY = [...VIEW, '0_primary'];
|
||||
export const VIEW_APPEARANCE = [...VIEW, '1_appearance'];
|
||||
export const VIEW_APPEARANCE_SUBMENU = [...VIEW_APPEARANCE, '1_appearance_submenu'];
|
||||
export const VIEW_APPEARANCE_SUBMENU_SCREEN = [...VIEW_APPEARANCE_SUBMENU, '2_appearance_submenu_screen'];
|
||||
export const VIEW_APPEARANCE_SUBMENU_BAR = [...VIEW_APPEARANCE_SUBMENU, '3_appearance_submenu_bar'];
|
||||
export const VIEW_EDITOR_SUBMENU = [...VIEW_APPEARANCE, '2_editor_submenu'];
|
||||
export const VIEW_EDITOR_SUBMENU_SPLIT = [...VIEW_EDITOR_SUBMENU, '1_editor_submenu_split'];
|
||||
export const VIEW_EDITOR_SUBMENU_ORTHO = [...VIEW_EDITOR_SUBMENU, '2_editor_submenu_ortho'];
|
||||
export const VIEW_VIEWS = [...VIEW, '2_views'];
|
||||
export const VIEW_LAYOUT = [...VIEW, '3_layout'];
|
||||
export const VIEW_TOGGLE = [...VIEW, '4_toggle'];
|
||||
|
||||
export const MANAGE_GENERAL = [...MANAGE_MENU, '1_manage_general'];
|
||||
export const MANAGE_SETTINGS = [...MANAGE_MENU, '2_manage_settings'];
|
||||
export const MANAGE_SETTINGS_THEMES = [...MANAGE_SETTINGS, '1_manage_settings_themes'];
|
||||
|
||||
// last menu item
|
||||
export const HELP = [...MAIN_MENU_BAR, '9_help'];
|
||||
|
||||
}
|
||||
361
packages/core/src/browser/common-styling-participants.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2022 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, interfaces } from 'inversify';
|
||||
import { ColorTheme, CssStyleCollector, StylingParticipant } from './styling-service';
|
||||
import { isHighContrast } from '../common/theme';
|
||||
|
||||
export function bindCommonStylingParticipants(bind: interfaces.Bind): void {
|
||||
for (const participant of [
|
||||
ActionLabelStylingParticipant,
|
||||
BadgeStylingParticipant,
|
||||
BreadcrumbStylingParticipant,
|
||||
ButtonStylingParticipant,
|
||||
MenuStylingParticipant,
|
||||
TabbarStylingParticipant,
|
||||
TreeStylingParticipant,
|
||||
StatusBarStylingParticipant
|
||||
]) {
|
||||
bind(participant).toSelf().inSingletonScope();
|
||||
bind(StylingParticipant).toService(participant);
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ActionLabelStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && focusBorder) {
|
||||
if (focusBorder) {
|
||||
collector.addRule(`
|
||||
.action-label:hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TreeStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && focusBorder) {
|
||||
collector.addRule(`
|
||||
.theia-TreeNode {
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.theia-TreeNode:hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
.theia-Tree .theia-TreeNode.theia-mod-selected {
|
||||
outline: 1px dotted ${focusBorder};
|
||||
}
|
||||
.theia-Tree:focus .theia-TreeNode.theia-mod-selected,
|
||||
.theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
|
||||
outline: 1px solid ${focusBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BreadcrumbStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const contrastBorder = theme.getColor('contrastBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && contrastBorder) {
|
||||
collector.addRule(`
|
||||
.theia-tabBar-breadcrumb-row {
|
||||
outline: 1px solid ${contrastBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class StatusBarStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && focusBorder) {
|
||||
collector.addRule(`
|
||||
#theia-statusBar .area .element.hasCommand:hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
#theia-statusBar .area .element.hasCommand:active {
|
||||
outline: 1px solid ${focusBorder};
|
||||
}
|
||||
.theia-mod-offline #theia-statusBar .area .element.hasCommand:hover {
|
||||
outline: none;
|
||||
}
|
||||
.theia-mod-offline #theia-statusBar .area .element.hasCommand:active {
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
} else {
|
||||
collector.addRule(`
|
||||
#theia-statusBar .area .element.hasCommand:hover {
|
||||
background-color: var(--theia-statusBarItem-hoverBackground);
|
||||
}
|
||||
#theia-statusBar .area .element.hasCommand:active {
|
||||
background-color: var(--theia-statusBarItem-activeBackground);
|
||||
}
|
||||
.theia-mod-offline #theia-statusBar .area .element.hasCommand:hover {
|
||||
background-color: var(--theia-statusBarItem-offlineHoverBackground) !important;
|
||||
}
|
||||
.theia-mod-offline #theia-statusBar .area .element.hasCommand:active {
|
||||
background-color: var(--theia-statusBarItem-offlineActiveBackground) !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MenuStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && focusBorder) {
|
||||
// Menu items
|
||||
collector.addRule(`
|
||||
.lm-Menu .lm-Menu-item.lm-mod-active {
|
||||
outline: 1px solid ${focusBorder};
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.lm-MenuBar .lm-MenuBar-item.lm-mod-active {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
.lm-MenuBar.lm-mod-active .lm-MenuBar-item.lm-mod-active {
|
||||
outline: 1px solid ${focusBorder};
|
||||
}
|
||||
`);
|
||||
// Sidebar items
|
||||
collector.addRule(`
|
||||
.theia-sidebar-menu > :hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
outline-offset: -7px;
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BadgeStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const contrastBorder = theme.getColor('contrastBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && contrastBorder) {
|
||||
collector.addRule(`.lm-TabBar .theia-badge-decorator-sidebar {
|
||||
outline: 1px solid ${contrastBorder};
|
||||
}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TabbarStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
const contrastBorder = theme.getColor('contrastBorder');
|
||||
const highContrast = isHighContrast(theme.type);
|
||||
|
||||
if (highContrast && focusBorder) {
|
||||
collector.addRule(`
|
||||
#theia-bottom-content-panel .lm-TabBar .lm-TabBar-tab,
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab {
|
||||
outline-offset: -4px;
|
||||
}
|
||||
#theia-bottom-content-panel .lm-TabBar .lm-TabBar-tab.lm-mod-current,
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab.lm-mod-current {
|
||||
outline: 1px solid ${focusBorder};
|
||||
}
|
||||
#theia-bottom-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab.lm-mod-current,
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab.lm-mod-current {
|
||||
outline: 1px dotted ${focusBorder};
|
||||
}
|
||||
#theia-bottom-content-panel .lm-TabBar .lm-TabBar-tab:not(.lm-mod-current):hover,
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab:not(.lm-mod-current):hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
const tabActiveBackground = theme.getColor('tab.activeBackground');
|
||||
const tabActiveBorderTop = theme.getColor('tab.activeBorderTop');
|
||||
const tabUnfocusedActiveBorderTop = theme.getColor('tab.unfocusedActiveBorderTop');
|
||||
const tabActiveBorder = theme.getColor('tab.activeBorder') || (highContrast && contrastBorder) || 'transparent';
|
||||
const tabUnfocusedActiveBorder = theme.getColor('tab.unfocusedActiveBorder') || (highContrast && contrastBorder) || 'transparent';
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab.lm-mod-current {
|
||||
color: var(--theia-tab-activeForeground);
|
||||
${tabActiveBackground ? `background: ${tabActiveBackground};` : ''}
|
||||
${tabActiveBorderTop ? `border-top: 1px solid ${tabActiveBorderTop};` : ''}
|
||||
border-bottom: 1px solid ${tabActiveBorder};
|
||||
}
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab.lm-mod-current {
|
||||
background: var(--theia-tab-unfocusedActiveBackground);
|
||||
color: var(--theia-tab-unfocusedActiveForeground);
|
||||
${tabUnfocusedActiveBorderTop ? `border-top: 1px solid ${tabUnfocusedActiveBorderTop};` : ''}
|
||||
border-bottom: 1px solid ${tabUnfocusedActiveBorder};
|
||||
}
|
||||
`);
|
||||
|
||||
// Highlight Modified Tabs
|
||||
const tabActiveModifiedBorder = theme.getColor('tab.activeModifiedBorder');
|
||||
const tabUnfocusedInactiveModifiedBorder = theme.getColor('tab.unfocusedInactiveModifiedBorder');
|
||||
const tabInactiveModifiedBorder = theme.getColor('tab.inactiveModifiedBorder');
|
||||
if (tabActiveModifiedBorder || tabInactiveModifiedBorder) {
|
||||
collector.addRule(`
|
||||
body.theia-editor-highlightModifiedTabs
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab.theia-mod-dirty {
|
||||
border-top: 2px solid ${tabInactiveModifiedBorder};
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
body.theia-editor-highlightModifiedTabs
|
||||
#theia-main-content-panel .lm-TabBar.theia-tabBar-active .lm-TabBar-tab.theia-mod-dirty.lm-mod-current {
|
||||
border-top: 2px solid ${tabActiveModifiedBorder};
|
||||
}
|
||||
|
||||
body.theia-editor-highlightModifiedTabs
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab.theia-mod-dirty:not(.lm-mod-current) {
|
||||
border-top: 2px solid ${tabUnfocusedInactiveModifiedBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Activity Bar Active Border
|
||||
const activityBarActiveBorder = theme.getColor('activityBar.activeBorder') || 'var(--theia-activityBar-foreground)';
|
||||
collector.addRule(`
|
||||
.lm-TabBar.theia-app-left .lm-TabBar-tab.lm-mod-current {
|
||||
border-top-color: transparent;
|
||||
box-shadow: 2px 0 0 ${activityBarActiveBorder} inset;
|
||||
}
|
||||
|
||||
.lm-TabBar.theia-app-right .lm-TabBar-tab.lm-mod-current {
|
||||
border-top-color: transparent;
|
||||
box-shadow: -2px 0 0 ${activityBarActiveBorder} inset;
|
||||
}
|
||||
`);
|
||||
// Hover Background
|
||||
const tabHoverBackground = theme.getColor('tab.hoverBackground');
|
||||
if (tabHoverBackground) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab:hover {
|
||||
background-color: ${tabHoverBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const tabUnfocusedHoverBackground = theme.getColor('tab.unfocusedHoverBackground');
|
||||
if (tabUnfocusedHoverBackground) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab:hover {
|
||||
background-color: ${tabUnfocusedHoverBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Hover Foreground
|
||||
const tabHoverForeground = theme.getColor('tab.hoverForeground');
|
||||
if (tabHoverForeground) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab:hover {
|
||||
color: ${tabHoverForeground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const tabUnfocusedHoverForeground = theme.getColor('tab.unfocusedHoverForeground');
|
||||
if (tabUnfocusedHoverForeground) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab:hover {
|
||||
color: ${tabUnfocusedHoverForeground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Hover Border
|
||||
const tabHoverBorder = theme.getColor('tab.hoverBorder');
|
||||
if (tabHoverBorder) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar .lm-TabBar-tab:hover {
|
||||
box-shadow: 0 1px 0 ${tabHoverBorder} inset;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const tabUnfocusedHoverBorder = theme.getColor('tab.unfocusedHoverBorder');
|
||||
if (tabUnfocusedHoverBorder) {
|
||||
collector.addRule(`
|
||||
#theia-main-content-panel .lm-TabBar:not(.theia-tabBar-active) .lm-TabBar-tab:hover {
|
||||
box-shadow: 0 1px 0 ${tabUnfocusedHoverBorder} inset;
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ButtonStylingParticipant implements StylingParticipant {
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
const contrastBorder = theme.getColor('contrastBorder');
|
||||
|
||||
if (isHighContrast(theme.type) && contrastBorder) {
|
||||
collector.addRule(`
|
||||
.theia-button {
|
||||
border: 1px solid ${contrastBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const buttonBackground = theme.getColor('button.background');
|
||||
collector.addRule(`
|
||||
.theia-button {
|
||||
background: ${buttonBackground || 'none'};
|
||||
}
|
||||
`);
|
||||
const buttonHoverBackground = theme.getColor('button.hoverBackground');
|
||||
if (buttonHoverBackground) {
|
||||
collector.addRule(`
|
||||
.theia-button:hover {
|
||||
background-color: ${buttonHoverBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
const secondaryButtonBackground = theme.getColor('secondaryButton.background');
|
||||
collector.addRule(`
|
||||
.theia-button.secondary {
|
||||
background: ${secondaryButtonBackground || 'none'};
|
||||
}
|
||||
`);
|
||||
const secondaryButtonHoverBackground = theme.getColor('secondaryButton.hoverBackground');
|
||||
if (secondaryButtonHoverBackground) {
|
||||
collector.addRule(`
|
||||
.theia-button.secondary:hover {
|
||||
background-color: ${secondaryButtonHoverBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
200
packages/core/src/browser/connection-status-service.spec.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { enableJSDOM } from '../browser/test/jsdom';
|
||||
|
||||
let disableJSDOM = enableJSDOM();
|
||||
|
||||
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
|
||||
FrontendApplicationConfigProvider.set({});
|
||||
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
ConnectionStatus,
|
||||
ConnectionStatusOptions,
|
||||
FrontendConnectionStatusService,
|
||||
PingService
|
||||
} from './connection-status-service';
|
||||
import { MockConnectionStatusService } from './test/mock-connection-status-service';
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { Container } from 'inversify';
|
||||
import { ILogger, Emitter, Loggable } from '../common';
|
||||
import { WebSocketConnectionSource } from './messaging/ws-connection-source';
|
||||
|
||||
disableJSDOM();
|
||||
|
||||
describe('connection-status', function (): void {
|
||||
|
||||
let connectionStatusService: MockConnectionStatusService;
|
||||
|
||||
before(() => {
|
||||
disableJSDOM = enableJSDOM();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
disableJSDOM();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
connectionStatusService = new MockConnectionStatusService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (connectionStatusService !== undefined) {
|
||||
connectionStatusService.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it('should go from online to offline if the connection is down', async () => {
|
||||
expect(connectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
connectionStatusService.alive = false;
|
||||
await pause();
|
||||
|
||||
expect(connectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should go from offline to online if the connection is re-established', async () => {
|
||||
expect(connectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
connectionStatusService.alive = false;
|
||||
await pause();
|
||||
expect(connectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
|
||||
connectionStatusService.alive = true;
|
||||
await pause();
|
||||
expect(connectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('frontend-connection-status', function (): void {
|
||||
const OFFLINE_TIMEOUT = 10;
|
||||
|
||||
let testContainer: Container;
|
||||
|
||||
const mockSocketOpenedEmitter: Emitter<void> = new Emitter();
|
||||
const mockSocketClosedEmitter: Emitter<void> = new Emitter();
|
||||
const mockIncomingMessageActivityEmitter: Emitter<void> = new Emitter();
|
||||
|
||||
before(() => {
|
||||
disableJSDOM = enableJSDOM();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
disableJSDOM();
|
||||
});
|
||||
|
||||
let timer: sinon.SinonFakeTimers;
|
||||
let pingSpy: sinon.SinonSpy;
|
||||
beforeEach(() => {
|
||||
const mockWebSocketConnectionSource = sinon.createStubInstance(WebSocketConnectionSource);
|
||||
const mockPingService: PingService = <PingService>{
|
||||
ping(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
};
|
||||
const mockILogger: ILogger = <ILogger>{
|
||||
error(loggable: Loggable): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
testContainer = new Container();
|
||||
testContainer.bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
|
||||
testContainer.bind(PingService).toConstantValue(mockPingService);
|
||||
testContainer.bind(ILogger).toConstantValue(mockILogger);
|
||||
testContainer.bind(ConnectionStatusOptions).toConstantValue({ offlineTimeout: OFFLINE_TIMEOUT });
|
||||
testContainer.bind(WebSocketConnectionSource).toConstantValue(mockWebSocketConnectionSource);
|
||||
|
||||
sinon.stub(mockWebSocketConnectionSource, 'onSocketDidOpen').value(mockSocketOpenedEmitter.event);
|
||||
sinon.stub(mockWebSocketConnectionSource, 'onSocketDidClose').value(mockSocketClosedEmitter.event);
|
||||
sinon.stub(mockWebSocketConnectionSource, 'onIncomingMessageActivity').value(mockIncomingMessageActivityEmitter.event);
|
||||
|
||||
timer = sinon.useFakeTimers();
|
||||
|
||||
pingSpy = sinon.spy(mockPingService, 'ping');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
pingSpy.restore();
|
||||
timer.restore();
|
||||
testContainer.unbindAll();
|
||||
});
|
||||
|
||||
it('should switch status to offline on websocket close', () => {
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
mockSocketClosedEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should switch status to online on websocket established', () => {
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
mockSocketClosedEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
mockSocketOpenedEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
});
|
||||
|
||||
it('should switch status to online on any websocket activity', () => {
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
mockSocketClosedEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
mockIncomingMessageActivityEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
});
|
||||
|
||||
it('should perform ping request after socket activity', () => {
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
mockIncomingMessageActivityEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
sinon.assert.notCalled(pingSpy);
|
||||
timer.tick(OFFLINE_TIMEOUT);
|
||||
sinon.assert.calledOnce(pingSpy);
|
||||
});
|
||||
|
||||
it('should not perform ping request before desired timeout', () => {
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
mockIncomingMessageActivityEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
sinon.assert.notCalled(pingSpy);
|
||||
timer.tick(OFFLINE_TIMEOUT - 1);
|
||||
sinon.assert.notCalled(pingSpy);
|
||||
});
|
||||
|
||||
it('should switch to offline mode if ping request was rejected', () => {
|
||||
const pingService = testContainer.get<PingService>(PingService);
|
||||
pingSpy.restore();
|
||||
const stub = sinon.stub(pingService, 'ping').onFirstCall().throws('failed to make a ping request');
|
||||
const frontendConnectionStatusService = testContainer.get<FrontendConnectionStatusService>(FrontendConnectionStatusService);
|
||||
frontendConnectionStatusService['init']();
|
||||
mockIncomingMessageActivityEmitter.fire(undefined);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.ONLINE);
|
||||
timer.tick(OFFLINE_TIMEOUT);
|
||||
sinon.assert.calledOnce(stub);
|
||||
expect(frontendConnectionStatusService.currentStatus).to.be.equal(ConnectionStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
|
||||
function pause(time: number = 1): Promise<unknown> {
|
||||
return new Promise(resolve => setTimeout(resolve, time));
|
||||
}
|
||||
216
packages/core/src/browser/connection-status-service.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, optional, postConstruct } from 'inversify';
|
||||
import { ILogger } from '../common/logger';
|
||||
import { Event, Emitter } from '../common/event';
|
||||
import { DefaultFrontendApplicationContribution } from './frontend-application-contribution';
|
||||
import { StatusBar, StatusBarAlignment } from './status-bar/status-bar';
|
||||
import { Disposable, DisposableCollection, nls } from '../common';
|
||||
import { WebSocketConnectionSource } from './messaging/ws-connection-source';
|
||||
|
||||
/**
|
||||
* Service for listening on backend connection changes.
|
||||
*/
|
||||
export const ConnectionStatusService = Symbol('ConnectionStatusService');
|
||||
export interface ConnectionStatusService {
|
||||
|
||||
/**
|
||||
* The actual connection status.
|
||||
*/
|
||||
readonly currentStatus: ConnectionStatus;
|
||||
|
||||
/**
|
||||
* Clients can listen on connection status change events.
|
||||
*/
|
||||
readonly onStatusChange: Event<ConnectionStatus>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection status.
|
||||
*/
|
||||
export enum ConnectionStatus {
|
||||
|
||||
/**
|
||||
* Connected to the backend.
|
||||
*/
|
||||
ONLINE,
|
||||
|
||||
/**
|
||||
* The connection is lost between frontend and backend.
|
||||
*/
|
||||
OFFLINE
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ConnectionStatusOptions {
|
||||
|
||||
static DEFAULT: ConnectionStatusOptions = {
|
||||
offlineTimeout: 5000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout in milliseconds before the application is considered offline. Must be a positive integer.
|
||||
*/
|
||||
readonly offlineTimeout: number;
|
||||
|
||||
}
|
||||
|
||||
export const PingService = Symbol('PingService');
|
||||
export interface PingService {
|
||||
ping(): Promise<void>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class AbstractConnectionStatusService implements ConnectionStatusService, Disposable {
|
||||
|
||||
protected readonly statusChangeEmitter = new Emitter<ConnectionStatus>();
|
||||
|
||||
protected connectionStatus: ConnectionStatus = ConnectionStatus.ONLINE;
|
||||
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
constructor(@inject(ConnectionStatusOptions) @optional() protected readonly options: ConnectionStatusOptions = ConnectionStatusOptions.DEFAULT) { }
|
||||
|
||||
get onStatusChange(): Event<ConnectionStatus> {
|
||||
return this.statusChangeEmitter.event;
|
||||
}
|
||||
|
||||
get currentStatus(): ConnectionStatus {
|
||||
return this.connectionStatus;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.statusChangeEmitter.dispose();
|
||||
}
|
||||
|
||||
protected updateStatus(success: boolean): void {
|
||||
const previousStatus = this.connectionStatus;
|
||||
const newStatus = success ? ConnectionStatus.ONLINE : ConnectionStatus.OFFLINE;
|
||||
if (previousStatus !== newStatus) {
|
||||
this.connectionStatus = newStatus;
|
||||
this.fireStatusChange(newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
protected fireStatusChange(status: ConnectionStatus): void {
|
||||
this.statusChangeEmitter.fire(status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class FrontendConnectionStatusService extends AbstractConnectionStatusService {
|
||||
|
||||
private scheduledPing: number | undefined;
|
||||
|
||||
@inject(WebSocketConnectionSource) protected readonly wsConnectionProvider: WebSocketConnectionSource;
|
||||
@inject(PingService) protected readonly pingService: PingService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.wsConnectionProvider.onSocketDidOpen(() => {
|
||||
this.updateStatus(true);
|
||||
this.schedulePing();
|
||||
});
|
||||
this.wsConnectionProvider.onSocketDidClose(() => {
|
||||
this.clearTimeout(this.scheduledPing);
|
||||
this.updateStatus(false);
|
||||
});
|
||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||
// natural activity
|
||||
this.updateStatus(true);
|
||||
this.schedulePing();
|
||||
});
|
||||
}
|
||||
|
||||
protected schedulePing(): void {
|
||||
this.clearTimeout(this.scheduledPing);
|
||||
this.scheduledPing = this.setTimeout(async () => {
|
||||
await this.performPingRequest();
|
||||
this.schedulePing();
|
||||
}, this.options.offlineTimeout);
|
||||
}
|
||||
|
||||
protected async performPingRequest(): Promise<void> {
|
||||
try {
|
||||
await this.pingService.ping();
|
||||
this.updateStatus(true);
|
||||
} catch (e) {
|
||||
this.updateStatus(false);
|
||||
await this.logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected setTimeout(handler: (...args: any[]) => void, timeout: number): number {
|
||||
return window.setTimeout(handler, timeout);
|
||||
}
|
||||
|
||||
protected clearTimeout(handle?: number): void {
|
||||
if (handle !== undefined) {
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ApplicationConnectionStatusContribution extends DefaultFrontendApplicationContribution {
|
||||
|
||||
protected readonly toDisposeOnOnline = new DisposableCollection();
|
||||
|
||||
constructor(
|
||||
@inject(ConnectionStatusService) protected readonly connectionStatusService: ConnectionStatusService,
|
||||
@inject(StatusBar) protected readonly statusBar: StatusBar,
|
||||
@inject(ILogger) protected readonly logger: ILogger
|
||||
) {
|
||||
super();
|
||||
this.connectionStatusService.onStatusChange(state => this.onStateChange(state));
|
||||
}
|
||||
|
||||
protected onStateChange(state: ConnectionStatus): void {
|
||||
switch (state) {
|
||||
case ConnectionStatus.OFFLINE: {
|
||||
this.handleOffline();
|
||||
break;
|
||||
}
|
||||
case ConnectionStatus.ONLINE: {
|
||||
this.handleOnline();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private statusbarId = 'connection-status';
|
||||
|
||||
protected handleOnline(): void {
|
||||
this.toDisposeOnOnline.dispose();
|
||||
}
|
||||
|
||||
protected handleOffline(): void {
|
||||
this.statusBar.setElement(this.statusbarId, {
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
text: nls.localize('theia/core/offline', 'Offline'),
|
||||
tooltip: nls.localize('theia/localize/offlineTooltip', 'Cannot connect to backend.'),
|
||||
priority: 5000
|
||||
});
|
||||
this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement(this.statusbarId)));
|
||||
document.body.classList.add('theia-mod-offline');
|
||||
this.toDisposeOnOnline.push(Disposable.create(() => document.body.classList.remove('theia-mod-offline')));
|
||||
}
|
||||
}
|
||||
168
packages/core/src/browser/context-key-service.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from 'inversify';
|
||||
import { Emitter, Event } from '../common/event';
|
||||
import { Disposable } from '../common';
|
||||
|
||||
export type ContextKeyValue = null | undefined | boolean | number | string
|
||||
| Array<null | undefined | boolean | number | string>
|
||||
| Record<string, null | undefined | boolean | number | string>;
|
||||
|
||||
export interface ContextKey<T extends ContextKeyValue = ContextKeyValue> {
|
||||
set(value: T | undefined): void;
|
||||
reset(): void;
|
||||
get(): T | undefined;
|
||||
}
|
||||
|
||||
export namespace ContextKey {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const None: ContextKey<any> = Object.freeze({
|
||||
set: () => { },
|
||||
reset: () => { },
|
||||
get: () => undefined
|
||||
});
|
||||
}
|
||||
|
||||
export interface ContextKeyChangeEvent {
|
||||
affects(keys: { has(key: string): boolean }): boolean;
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
getValue<T extends ContextKeyValue = ContextKeyValue>(key: string): T | undefined;
|
||||
readonly onDidChange?: Event<ContextKeyChangeEvent>;
|
||||
}
|
||||
|
||||
export const ContextKeyService = Symbol('ContextKeyService');
|
||||
|
||||
export interface ContextMatcher {
|
||||
/**
|
||||
* Whether the expression is satisfied. If `context` provided, the service will attempt to retrieve a context object associated with that element.
|
||||
*/
|
||||
match(expression: string, context?: HTMLElement): boolean;
|
||||
}
|
||||
|
||||
export interface ContextKeyService extends ContextMatcher {
|
||||
readonly onDidChange: Event<ContextKeyChangeEvent>;
|
||||
|
||||
createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): ContextKey<T>;
|
||||
|
||||
/**
|
||||
* @returns a Set of the keys used by the given `expression` or `undefined` if none are used or the expression cannot be parsed.
|
||||
*/
|
||||
parseKeys(expression: string): Set<string> | undefined;
|
||||
|
||||
/**
|
||||
* Creates a temporary context that will use the `values` passed in when evaluating {@link callback}.
|
||||
* {@link callback | The callback} must be synchronous.
|
||||
*/
|
||||
with<T>(values: Record<string, unknown>, callback: () => T): T;
|
||||
|
||||
/**
|
||||
* Creates a child service with a separate context scoped to the HTML element passed in.
|
||||
* Useful for e.g. setting the {view} context value for particular widgets.
|
||||
*/
|
||||
createScoped(target: HTMLElement): ScopedValueStore;
|
||||
|
||||
/**
|
||||
* @param overlay values to be used in the new {@link ContextKeyService}. These values will be static.
|
||||
* Creates a child service with a separate context and a set of fixed values to override parent values.
|
||||
*/
|
||||
createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher;
|
||||
|
||||
/**
|
||||
* Set or modify a value in the service's context.
|
||||
*/
|
||||
setContext(key: string, value: unknown): void;
|
||||
|
||||
/**
|
||||
* Gets the context keys that are locally defined (not inherited from parent contexts)
|
||||
* at or above the given element. This is useful for determining which context keys
|
||||
* are specific to a particular DOM element's scope.
|
||||
*
|
||||
* @param element The DOM element to get local context keys for
|
||||
* @returns A Set of context key names that are locally defined, or an empty Set if none
|
||||
*/
|
||||
getLocalContextKeys(element: HTMLElement): Set<string>;
|
||||
}
|
||||
|
||||
export type ScopedValueStore = Omit<ContextKeyService, 'onDidChange' | 'match' | 'parseKeys' | 'with' | 'createOverlay'> & Disposable & {
|
||||
onDidChangeContext: Event<ContextKeyChangeEvent>;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class ContextKeyServiceDummyImpl implements ContextKeyService {
|
||||
protected readonly onDidChangeEmitter = new Emitter<ContextKeyChangeEvent>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
protected fireDidChange(event: ContextKeyChangeEvent): void {
|
||||
this.onDidChangeEmitter.fire(event);
|
||||
}
|
||||
|
||||
onDidChangeContext: Event<ContextKeyChangeEvent> = this.onDidChangeEmitter.event;
|
||||
|
||||
createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): ContextKey<T> {
|
||||
return ContextKey.None;
|
||||
}
|
||||
/**
|
||||
* It should be implemented by an extension, e.g. by the monaco extension.
|
||||
*/
|
||||
match(expression: string, context?: HTMLElement): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* It should be implemented by an extension, e.g. by the monaco extension.
|
||||
*/
|
||||
parseKeys(expression: string): Set<string> | undefined {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Details should be implemented by an extension, e.g. by the monaco extension.
|
||||
* Callback must be synchronous.
|
||||
*/
|
||||
with<T>(values: Record<string, unknown>, callback: () => T): T {
|
||||
return callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Details should implemented by an extension, e.g. by the monaco extension.
|
||||
*/
|
||||
createScoped(target: HTMLElement): ScopedValueStore {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Details should be implemented by an extension, e.g. the monaco extension.
|
||||
*/
|
||||
createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Details should be implemented by an extension, e.g. by the monaco extension.
|
||||
*/
|
||||
setContext(key: string, value: unknown): void { }
|
||||
|
||||
/**
|
||||
* It should be implemented by an extension, e.g. by the monaco extension.
|
||||
*/
|
||||
getLocalContextKeys(element: HTMLElement): Set<string> {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
158
packages/core/src/browser/context-menu-renderer.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { CompoundMenuNode, GroupImpl, MenuModelRegistry, MenuPath } from '../common/menu';
|
||||
import { Disposable, DisposableCollection } from '../common/disposable';
|
||||
import { ContextKeyService, ContextMatcher } from './context-key-service';
|
||||
|
||||
export interface Coordinate { x: number; y: number; }
|
||||
export const Coordinate = Symbol('Coordinate');
|
||||
|
||||
export type Anchor = MouseEvent | Coordinate;
|
||||
|
||||
export function coordinateFromAnchor(anchor: Anchor): Coordinate {
|
||||
const { x, y } = anchor instanceof MouseEvent ? { x: anchor.clientX, y: anchor.clientY } : anchor;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
export class ContextMenuAccess implements Disposable {
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
readonly onDispose = this.toDispose.onDispose;
|
||||
|
||||
constructor(toClose: Disposable) {
|
||||
this.toDispose.push(toClose);
|
||||
}
|
||||
|
||||
get disposed(): boolean {
|
||||
return this.toDispose.disposed;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class ContextMenuRenderer {
|
||||
|
||||
@inject(MenuModelRegistry) menuRegistry: MenuModelRegistry;
|
||||
@inject(ContextKeyService)
|
||||
protected readonly contextKeyService: ContextKeyService;
|
||||
|
||||
protected _current: ContextMenuAccess | undefined;
|
||||
protected readonly toDisposeOnSetCurrent = new DisposableCollection();
|
||||
/**
|
||||
* Currently opened context menu.
|
||||
* Rendering a new context menu will close the current.
|
||||
*/
|
||||
get current(): ContextMenuAccess | undefined {
|
||||
return this._current;
|
||||
}
|
||||
set current(current: ContextMenuAccess | undefined) {
|
||||
this.setCurrent(current);
|
||||
}
|
||||
protected setCurrent(current: ContextMenuAccess | undefined): void {
|
||||
if (this._current === current) {
|
||||
return;
|
||||
}
|
||||
this.toDisposeOnSetCurrent.dispose();
|
||||
this._current = current;
|
||||
if (current) {
|
||||
this.toDisposeOnSetCurrent.push(current.onDispose(() => {
|
||||
this._current = undefined;
|
||||
}));
|
||||
this.toDisposeOnSetCurrent.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
render(options: RenderContextMenuOptions): ContextMenuAccess {
|
||||
let menu = options.menu;
|
||||
if (!menu) {
|
||||
menu = this.menuRegistry.getMenu(options.menuPath) || new GroupImpl('emtpyContextMenu');
|
||||
}
|
||||
|
||||
const resolvedOptions = this.resolve(options);
|
||||
|
||||
if (resolvedOptions.skipSingleRootNode) {
|
||||
menu = MenuModelRegistry.removeSingleRootNode(menu);
|
||||
}
|
||||
|
||||
const access = this.doRender({
|
||||
menuPath: options.menuPath,
|
||||
menu,
|
||||
anchor: resolvedOptions.anchor,
|
||||
contextMatcher: options.contextKeyService || this.contextKeyService,
|
||||
args: resolvedOptions.args,
|
||||
context: resolvedOptions.context,
|
||||
onHide: resolvedOptions.onHide
|
||||
});
|
||||
this.setCurrent(access);
|
||||
return access;
|
||||
}
|
||||
|
||||
protected abstract doRender(params: {
|
||||
menuPath: MenuPath,
|
||||
menu: CompoundMenuNode,
|
||||
anchor: Anchor,
|
||||
contextMatcher: ContextMatcher,
|
||||
args?: any[],
|
||||
context?: HTMLElement,
|
||||
onHide?: () => void
|
||||
}): ContextMenuAccess;
|
||||
|
||||
protected resolve(options: RenderContextMenuOptions): RenderContextMenuOptions {
|
||||
const args: any[] = options.args ? options.args.slice() : [];
|
||||
if (options.includeAnchorArg !== false) {
|
||||
args.push(options.anchor);
|
||||
}
|
||||
return {
|
||||
...options,
|
||||
args
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface RenderContextMenuOptions {
|
||||
menu?: CompoundMenuNode,
|
||||
menuPath: MenuPath;
|
||||
anchor: Anchor;
|
||||
args?: any[];
|
||||
/**
|
||||
* Whether the anchor should be passed as an argument to the handlers of commands for this context menu.
|
||||
* If true, the anchor will be appended to the list of arguments or passed as the only argument if no other
|
||||
* arguments are supplied.
|
||||
* Default is `true`.
|
||||
*/
|
||||
includeAnchorArg?: boolean;
|
||||
/**
|
||||
* A DOM context for the menu to be shown
|
||||
* Will be used to attach the menu to a window and to evaluate enablement ("when"-clauses)
|
||||
*/
|
||||
context: HTMLElement;
|
||||
contextKeyService?: ContextMatcher;
|
||||
onHide?: () => void;
|
||||
/**
|
||||
* If true a single submenu in the context menu is not rendered but its children are rendered on the top level.
|
||||
* Default is `false`.
|
||||
*/
|
||||
skipSingleRootNode?: boolean;
|
||||
}
|
||||
115
packages/core/src/browser/credentials-service.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 Red Hat, Inc. and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// code copied and modified from https://github.com/microsoft/vscode/blob/1.55.2/src/vs/workbench/services/credentials/common/credentials.ts#L12
|
||||
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Emitter, Event } from '../common/event';
|
||||
import { KeyStoreService } from '../common/key-store';
|
||||
|
||||
export interface CredentialsProvider {
|
||||
getPassword(service: string, account: string): Promise<string | undefined>;
|
||||
setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
deletePassword(service: string, account: string): Promise<boolean>;
|
||||
findPassword(service: string): Promise<string | undefined>;
|
||||
findCredentials(service: string): Promise<Array<{ account: string, password: string }>>;
|
||||
keys(service: string): Promise<string[]>;
|
||||
}
|
||||
|
||||
export const CredentialsService = Symbol('CredentialsService');
|
||||
|
||||
export interface CredentialsService extends CredentialsProvider {
|
||||
readonly onDidChangePassword: Event<CredentialsChangeEvent>;
|
||||
}
|
||||
|
||||
export interface CredentialsChangeEvent {
|
||||
service: string
|
||||
account: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class CredentialsServiceImpl implements CredentialsService {
|
||||
private onDidChangePasswordEmitter = new Emitter<CredentialsChangeEvent>();
|
||||
readonly onDidChangePassword = this.onDidChangePasswordEmitter.event;
|
||||
|
||||
private credentialsProvider: CredentialsProvider;
|
||||
|
||||
constructor(@inject(KeyStoreService) private readonly keytarService: KeyStoreService) {
|
||||
this.credentialsProvider = new KeytarCredentialsProvider(this.keytarService);
|
||||
}
|
||||
|
||||
getPassword(service: string, account: string): Promise<string | undefined> {
|
||||
return this.credentialsProvider.getPassword(service, account);
|
||||
}
|
||||
|
||||
async setPassword(service: string, account: string, password: string): Promise<void> {
|
||||
await this.credentialsProvider.setPassword(service, account, password);
|
||||
|
||||
this.onDidChangePasswordEmitter.fire({ service, account });
|
||||
}
|
||||
|
||||
deletePassword(service: string, account: string): Promise<boolean> {
|
||||
const didDelete = this.credentialsProvider.deletePassword(service, account);
|
||||
this.onDidChangePasswordEmitter.fire({ service, account });
|
||||
|
||||
return didDelete;
|
||||
}
|
||||
|
||||
findPassword(service: string): Promise<string | undefined> {
|
||||
return this.credentialsProvider.findPassword(service);
|
||||
}
|
||||
|
||||
findCredentials(service: string): Promise<Array<{ account: string, password: string; }>> {
|
||||
return this.credentialsProvider.findCredentials(service);
|
||||
}
|
||||
|
||||
async keys(service: string): Promise<string[]> {
|
||||
return this.credentialsProvider.keys(service);
|
||||
}
|
||||
}
|
||||
|
||||
class KeytarCredentialsProvider implements CredentialsProvider {
|
||||
|
||||
constructor(private readonly keytarService: KeyStoreService) { }
|
||||
|
||||
deletePassword(service: string, account: string): Promise<boolean> {
|
||||
return this.keytarService.deletePassword(service, account);
|
||||
}
|
||||
|
||||
findCredentials(service: string): Promise<Array<{ account: string; password: string }>> {
|
||||
return this.keytarService.findCredentials(service);
|
||||
}
|
||||
|
||||
findPassword(service: string): Promise<string | undefined> {
|
||||
return this.keytarService.findPassword(service);
|
||||
}
|
||||
|
||||
getPassword(service: string, account: string): Promise<string | undefined> {
|
||||
return this.keytarService.getPassword(service, account);
|
||||
}
|
||||
|
||||
setPassword(service: string, account: string, password: string): Promise<void> {
|
||||
return this.keytarService.setPassword(service, account, password);
|
||||
}
|
||||
|
||||
keys(service: string): Promise<string[]> {
|
||||
return this.keytarService.keys(service);
|
||||
}
|
||||
}
|
||||
65
packages/core/src/browser/decoration-style.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export namespace DecorationStyle {
|
||||
|
||||
export function createStyleElement(styleId: string, container: HTMLElement = document.head): HTMLStyleElement {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleId;
|
||||
style.type = 'text/css';
|
||||
style.media = 'screen';
|
||||
style.appendChild(document.createTextNode('')); // trick for webkit
|
||||
container.appendChild(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
export function createStyleSheet(styleId: string, container?: HTMLElement): CSSStyleSheet {
|
||||
return <CSSStyleSheet>createStyleElement(styleId, container).sheet;
|
||||
}
|
||||
|
||||
function getRuleIndex(selector: string, styleSheet: CSSStyleSheet): number {
|
||||
return Array.from(styleSheet.cssRules || styleSheet.rules).findIndex(rule => rule.type === CSSRule.STYLE_RULE && (<CSSStyleRule>rule).selectorText === selector);
|
||||
}
|
||||
|
||||
export function getOrCreateStyleRule(selector: string, styleSheet: CSSStyleSheet): CSSStyleRule {
|
||||
let index = getRuleIndex(selector, styleSheet);
|
||||
if (index === -1) {
|
||||
// The given selector does not exist in the provided independent style sheet, rule index = 0
|
||||
index = styleSheet.insertRule(selector + '{}', 0);
|
||||
}
|
||||
|
||||
const rules = styleSheet.cssRules || styleSheet.rules;
|
||||
const rule = rules[index];
|
||||
if (rule && rule.type === CSSRule.STYLE_RULE) {
|
||||
return rule as CSSStyleRule;
|
||||
}
|
||||
styleSheet.deleteRule(index);
|
||||
throw new Error('This function is only for CSS style rules. Other types of CSS rules are not allowed.');
|
||||
}
|
||||
|
||||
export function deleteStyleRule(selector: string, styleSheet: CSSStyleSheet): void {
|
||||
if (!styleSheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In general, only one rule exists for a given selector in the provided independent style sheet
|
||||
const index = getRuleIndex(selector, styleSheet);
|
||||
if (index !== -1) {
|
||||
styleSheet.deleteRule(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
209
packages/core/src/browser/decorations-service.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 Red Hat, Inc. and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from 'inversify';
|
||||
import { isThenable } from '../common/promise-util';
|
||||
import { CancellationToken, CancellationTokenSource, Disposable, Emitter, Event } from '../common';
|
||||
import { TernarySearchTree } from '../common/ternary-search-tree';
|
||||
import URI from '../common/uri';
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// some code copied and modified from https://github.com/microsoft/vscode/blob/1.52.1/src/vs/workbench/services/decorations/browser/decorationsService.ts#L24-L23
|
||||
|
||||
export interface DecorationsProvider {
|
||||
readonly onDidChange: Event<URI[]>;
|
||||
provideDecorations(uri: URI, token: CancellationToken): Decoration | Promise<Decoration | undefined> | undefined;
|
||||
}
|
||||
|
||||
export interface Decoration {
|
||||
readonly weight?: number;
|
||||
readonly colorId?: string;
|
||||
readonly letter?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly bubble?: boolean;
|
||||
}
|
||||
|
||||
export interface ResourceDecorationChangeEvent {
|
||||
affectsResource(uri: URI): boolean;
|
||||
}
|
||||
export const DecorationsService = Symbol('DecorationsService');
|
||||
export interface DecorationsService {
|
||||
|
||||
readonly onDidChangeDecorations: Event<Map<string, Decoration>>;
|
||||
|
||||
registerDecorationsProvider(provider: DecorationsProvider): Disposable;
|
||||
|
||||
getDecoration(uri: URI, includeChildren: boolean): Decoration[];
|
||||
}
|
||||
|
||||
class DecorationDataRequest {
|
||||
constructor(
|
||||
readonly source: CancellationTokenSource,
|
||||
readonly thenable: Promise<void>,
|
||||
) { }
|
||||
}
|
||||
|
||||
class DecorationProviderWrapper {
|
||||
|
||||
readonly data: TernarySearchTree<URI, DecorationDataRequest | Decoration | undefined>;
|
||||
readonly decorations: Map<string, Decoration> = new Map();
|
||||
private readonly disposable: Disposable;
|
||||
|
||||
constructor(
|
||||
readonly provider: DecorationsProvider,
|
||||
readonly onDidChangeDecorationsEmitter: Emitter<Map<string, Decoration>>
|
||||
) {
|
||||
|
||||
this.data = TernarySearchTree.forUris<DecorationDataRequest | Decoration | undefined>(true);
|
||||
|
||||
this.disposable = this.provider.onDidChange(async uris => {
|
||||
this.decorations.clear();
|
||||
if (!uris) {
|
||||
this.data.clear();
|
||||
} else {
|
||||
for (const uri of uris) {
|
||||
this.fetchData(uri);
|
||||
const decoration = await provider.provideDecorations(uri, CancellationToken.None);
|
||||
if (decoration) {
|
||||
this.decorations.set(uri.toString(), decoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.onDidChangeDecorationsEmitter.fire(this.decorations);
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
knowsAbout(uri: URI): boolean {
|
||||
return !!this.data.get(uri) || Boolean(this.data.findSuperstr(uri));
|
||||
}
|
||||
|
||||
getOrRetrieve(uri: URI, includeChildren: boolean, callback: (data: Decoration, isChild: boolean) => void): void {
|
||||
|
||||
let item = this.data.get(uri);
|
||||
|
||||
if (item === undefined) {
|
||||
// unknown -> trigger request
|
||||
item = this.fetchData(uri);
|
||||
}
|
||||
|
||||
if (item && !(item instanceof DecorationDataRequest)) {
|
||||
// found something (which isn't pending anymore)
|
||||
callback(item, false);
|
||||
}
|
||||
|
||||
if (includeChildren) {
|
||||
// (resolved) children
|
||||
const iter = this.data.findSuperstr(uri);
|
||||
if (iter) {
|
||||
let next = iter.next();
|
||||
while (!next.done) {
|
||||
const value = next.value;
|
||||
if (value && !(value instanceof DecorationDataRequest)) {
|
||||
callback(value, true);
|
||||
}
|
||||
next = iter.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fetchData(uri: URI): Decoration | undefined {
|
||||
|
||||
// check for pending request and cancel it
|
||||
const pendingRequest = this.data.get(uri);
|
||||
if (pendingRequest instanceof DecorationDataRequest) {
|
||||
pendingRequest.source.cancel();
|
||||
this.data.delete(uri);
|
||||
}
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
const dataOrThenable = this.provider.provideDecorations(uri, source.token);
|
||||
if (!isThenable<Decoration | Promise<Decoration | undefined> | undefined>(dataOrThenable)) {
|
||||
// sync -> we have a result now
|
||||
return this.keepItem(uri, dataOrThenable);
|
||||
|
||||
} else {
|
||||
// async -> we have a result soon
|
||||
const request = new DecorationDataRequest(source, Promise.resolve(dataOrThenable).then(data => {
|
||||
if (this.data.get(uri) === request) {
|
||||
this.keepItem(uri, data);
|
||||
}
|
||||
}).catch(err => {
|
||||
if (!(err instanceof Error && err.name === 'Canceled' && err.message === 'Canceled') && this.data.get(uri) === request) {
|
||||
this.data.delete(uri);
|
||||
}
|
||||
}));
|
||||
|
||||
this.data.set(uri, request);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private keepItem(uri: URI, data: Decoration | undefined): Decoration | undefined {
|
||||
const deco = data ? data : undefined;
|
||||
this.data.set(uri, deco);
|
||||
return deco;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DecorationsServiceImpl implements DecorationsService {
|
||||
|
||||
private readonly data: DecorationProviderWrapper[] = [];
|
||||
private readonly onDidChangeDecorationsEmitter = new Emitter<Map<string, Decoration>>();
|
||||
|
||||
readonly onDidChangeDecorations = this.onDidChangeDecorationsEmitter.event;
|
||||
|
||||
dispose(): void {
|
||||
this.onDidChangeDecorationsEmitter.dispose();
|
||||
}
|
||||
|
||||
registerDecorationsProvider(provider: DecorationsProvider): Disposable {
|
||||
|
||||
const wrapper = new DecorationProviderWrapper(provider, this.onDidChangeDecorationsEmitter);
|
||||
this.data.push(wrapper);
|
||||
|
||||
return Disposable.create(() => {
|
||||
// fire event that says 'yes' for any resource
|
||||
// known to this provider. then dispose and remove it.
|
||||
this.data.splice(this.data.indexOf(wrapper), 1);
|
||||
this.onDidChangeDecorationsEmitter.fire(new Map<string, Decoration>());
|
||||
wrapper.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
getDecoration(uri: URI, includeChildren: boolean): Decoration[] {
|
||||
const data: Decoration[] = [];
|
||||
let containsChildren: boolean = false;
|
||||
for (const wrapper of this.data) {
|
||||
wrapper.getOrRetrieve(uri, includeChildren, (deco, isChild) => {
|
||||
if (!isChild || deco.bubble) {
|
||||
data.push(deco);
|
||||
containsChildren = isChild || containsChildren;
|
||||
}
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
565
packages/core/src/browser/dialogs.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, unmanaged } from 'inversify';
|
||||
import { Disposable, MaybePromise, CancellationTokenSource, nls } from '../common';
|
||||
import { Key } from './keyboard/keys';
|
||||
import { Widget, BaseWidget, Message, addKeyListener, codiconArray } from './widgets/widget';
|
||||
import { FrontendApplicationContribution } from './frontend-application-contribution';
|
||||
|
||||
@injectable()
|
||||
export class DialogProps {
|
||||
readonly title: string;
|
||||
/**
|
||||
* Determines the maximum width of the dialog in pixels.
|
||||
* Default value is undefined, which would result in the css property 'max-width: none' being applied to the dialog.
|
||||
*/
|
||||
maxWidth?: number;
|
||||
/**
|
||||
* Determine the word wrapping behavior for content in the dialog.
|
||||
* - `normal`: breaks words at allowed break points.
|
||||
* - `break-word`: breaks otherwise unbreakable words.
|
||||
* - `initial`: sets the property to it's default value.
|
||||
* - `inherit`: inherit this property from it's parent element.
|
||||
* Default value is undefined, which would result in the css property 'word-wrap' not being applied to the dialog.
|
||||
*/
|
||||
wordWrap?: 'normal' | 'break-word' | 'initial' | 'inherit';
|
||||
}
|
||||
|
||||
export type DialogMode = 'open' | 'preview';
|
||||
|
||||
export type DialogError = string | boolean | {
|
||||
message: string
|
||||
result: boolean
|
||||
};
|
||||
export namespace DialogError {
|
||||
export function getResult(error: DialogError): boolean {
|
||||
if (typeof error === 'string') {
|
||||
return !error.length;
|
||||
}
|
||||
if (typeof error === 'boolean') {
|
||||
return error;
|
||||
}
|
||||
return error.result;
|
||||
}
|
||||
export function getMessage(error: DialogError): string {
|
||||
if (typeof error === 'string') {
|
||||
return error;
|
||||
}
|
||||
if (typeof error === 'boolean') {
|
||||
return '';
|
||||
}
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Dialog {
|
||||
export const YES = nls.localizeByDefault('Yes');
|
||||
export const NO = nls.localizeByDefault('No');
|
||||
export const OK = nls.localizeByDefault('OK');
|
||||
export const CANCEL = nls.localizeByDefault('Cancel');
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DialogOverlayService implements FrontendApplicationContribution {
|
||||
|
||||
protected static INSTANCE: DialogOverlayService;
|
||||
|
||||
static get(): DialogOverlayService {
|
||||
return DialogOverlayService.INSTANCE;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected readonly dialogs: AbstractDialog<any>[] = [];
|
||||
protected readonly documents: Document[] = [];
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
DialogOverlayService.INSTANCE = this;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected get currentDialog(): AbstractDialog<any> | undefined {
|
||||
return this.dialogs[0];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
push(dialog: AbstractDialog<any>): Disposable {
|
||||
if (this.documents.findIndex(document => document === dialog.node.ownerDocument) < 0) {
|
||||
addKeyListener(dialog.node.ownerDocument.body, Key.ENTER, e => this.handleEnter(e));
|
||||
addKeyListener(dialog.node.ownerDocument.body, Key.ESCAPE, e => this.handleEscape(e));
|
||||
this.documents.push(dialog.node.ownerDocument);
|
||||
}
|
||||
this.dialogs.unshift(dialog);
|
||||
return Disposable.create(() => {
|
||||
const index = this.dialogs.indexOf(dialog);
|
||||
if (index > -1) {
|
||||
this.dialogs.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected handleEscape(event: KeyboardEvent): boolean | void {
|
||||
const dialog = this.currentDialog;
|
||||
if (dialog) {
|
||||
return dialog['handleEscape'](event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||
const dialog = this.currentDialog;
|
||||
if (dialog) {
|
||||
return dialog['handleEnter'](event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class AbstractDialog<T> extends BaseWidget {
|
||||
|
||||
protected readonly titleNode: HTMLDivElement;
|
||||
protected readonly contentNode: HTMLDivElement;
|
||||
protected readonly closeCrossNode: HTMLElement;
|
||||
protected readonly controlPanel: HTMLDivElement;
|
||||
protected readonly errorMessageNode: HTMLDivElement;
|
||||
|
||||
protected resolve: undefined | ((value: T | undefined) => void);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected reject: undefined | ((reason: any) => void);
|
||||
|
||||
protected closeButton: HTMLButtonElement | undefined;
|
||||
protected acceptButton: HTMLButtonElement | undefined;
|
||||
|
||||
protected activeElement: HTMLElement | undefined;
|
||||
|
||||
constructor(
|
||||
@unmanaged() protected readonly props: DialogProps,
|
||||
@unmanaged() options?: Widget.IOptions
|
||||
) {
|
||||
super(options);
|
||||
this.id = 'theia-dialog-shell';
|
||||
this.addClass('dialogOverlay');
|
||||
this.toDispose.push(Disposable.create(() => {
|
||||
if (this.reject) {
|
||||
Widget.detach(this);
|
||||
}
|
||||
}));
|
||||
const container = this.node.ownerDocument.createElement('div');
|
||||
container.classList.add('dialogBlock');
|
||||
if (props.maxWidth === undefined) {
|
||||
container.setAttribute('style', 'max-width: none');
|
||||
} else if (props.maxWidth < 400) {
|
||||
container.setAttribute('style', `max-width: ${props.maxWidth}px; min-width: 0px`);
|
||||
} else {
|
||||
container.setAttribute('style', `max-width: ${props.maxWidth}px`);
|
||||
}
|
||||
this.node.appendChild(container);
|
||||
|
||||
const titleContentNode = this.node.ownerDocument.createElement('div');
|
||||
titleContentNode.classList.add('dialogTitle');
|
||||
container.appendChild(titleContentNode);
|
||||
|
||||
this.titleNode = this.node.ownerDocument.createElement('div');
|
||||
this.titleNode.textContent = props.title;
|
||||
titleContentNode.appendChild(this.titleNode);
|
||||
|
||||
this.closeCrossNode = this.node.ownerDocument.createElement('i');
|
||||
this.closeCrossNode.classList.add(...codiconArray('close', true));
|
||||
this.closeCrossNode.classList.add('closeButton');
|
||||
titleContentNode.appendChild(this.closeCrossNode);
|
||||
|
||||
this.contentNode = this.node.ownerDocument.createElement('div');
|
||||
this.contentNode.classList.add('dialogContent');
|
||||
if (props.wordWrap !== undefined) {
|
||||
this.contentNode.setAttribute('style', `word-wrap: ${props.wordWrap}`);
|
||||
}
|
||||
container.appendChild(this.contentNode);
|
||||
|
||||
this.controlPanel = this.node.ownerDocument.createElement('div');
|
||||
this.controlPanel.classList.add('dialogControl');
|
||||
container.appendChild(this.controlPanel);
|
||||
|
||||
this.errorMessageNode = this.node.ownerDocument.createElement('div');
|
||||
this.errorMessageNode.classList.add('error');
|
||||
this.errorMessageNode.setAttribute('style', 'flex: 2');
|
||||
this.controlPanel.appendChild(this.errorMessageNode);
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected appendCloseButton(text: string = Dialog.CANCEL): HTMLButtonElement {
|
||||
return this.closeButton = this.appendButton(text, false);
|
||||
}
|
||||
|
||||
protected appendAcceptButton(text: string = Dialog.OK): HTMLButtonElement {
|
||||
return this.acceptButton = this.appendButton(text, true);
|
||||
}
|
||||
|
||||
protected appendButton(text: string, primary: boolean): HTMLButtonElement {
|
||||
const button = this.createButton(text);
|
||||
this.controlPanel.appendChild(button);
|
||||
button.classList.add(primary ? 'main' : 'secondary');
|
||||
return button;
|
||||
}
|
||||
|
||||
protected createButton(text: string): HTMLButtonElement {
|
||||
const button = document.createElement('button');
|
||||
button.classList.add('theia-button');
|
||||
button.textContent = text;
|
||||
return button;
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
super.onAfterAttach(msg);
|
||||
if (this.closeButton) {
|
||||
this.addCloseAction(this.closeButton, 'click');
|
||||
}
|
||||
if (this.acceptButton) {
|
||||
this.addAcceptAction(this.acceptButton, 'click');
|
||||
}
|
||||
this.addCloseAction(this.closeCrossNode, 'click');
|
||||
this.toDisposeOnDetach.push(this.preventTabbingOutsideDialog());
|
||||
// TODO: use DI always to create dialog instances
|
||||
this.toDisposeOnDetach.push(DialogOverlayService.get().push(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* This prevents tabbing outside the dialog by marking elements as inert, i.e., non-clickable and non-focussable.
|
||||
*
|
||||
* @param elements the elements for which we disable tabbing. By default all elements within the body element are considered.
|
||||
* Please note that this may also include other popups such as the suggestion overlay, the notification center or quick picks.
|
||||
* @returns a disposable that will restore the previous tabbing behavior
|
||||
*/
|
||||
protected preventTabbingOutsideDialog(elements = Array.from(this.node.ownerDocument.body.children)): Disposable { //
|
||||
const inertBlacklist = ['select-component-container']; // IDs of elements that should remain interactive
|
||||
const nonInertElements = elements.filter(child => child !== this.node && !(child.hasAttribute('inert')) && !inertBlacklist.includes(child.id));
|
||||
nonInertElements.forEach(child => child.setAttribute('inert', ''));
|
||||
return Disposable.create(() => nonInertElements.forEach(child => child.removeAttribute('inert')));
|
||||
}
|
||||
|
||||
protected handleEscape(event: KeyboardEvent): boolean | void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||
if (event.target instanceof HTMLTextAreaElement) {
|
||||
return false;
|
||||
}
|
||||
this.accept();
|
||||
}
|
||||
|
||||
protected override onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
if (this.acceptButton) {
|
||||
this.acceptButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
open(disposeOnResolve: boolean = true): Promise<T | undefined> {
|
||||
if (this.resolve) {
|
||||
return Promise.reject(new Error('The dialog is already opened.'));
|
||||
}
|
||||
this.activeElement = this.node.ownerDocument.activeElement as HTMLElement;
|
||||
return new Promise<T | undefined>((resolve, reject) => {
|
||||
this.resolve = value => {
|
||||
resolve(value);
|
||||
};
|
||||
this.reject = reject;
|
||||
this.toDisposeOnDetach.push(Disposable.create(() => {
|
||||
this.resolve = undefined;
|
||||
this.reject = undefined;
|
||||
}));
|
||||
|
||||
Widget.attach(this, this.node.ownerDocument.body);
|
||||
this.activate();
|
||||
}).finally(() => {
|
||||
if (disposeOnResolve) {
|
||||
this.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override onCloseRequest(msg: Message): void {
|
||||
// super.onCloseRequest() would automatically dispose the dialog, which we don't want because we're reusing it
|
||||
if (this.parent) {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
this.parent = null;
|
||||
} else if (this.isAttached) {
|
||||
Widget.detach(this);
|
||||
}
|
||||
}
|
||||
|
||||
override close(): void {
|
||||
if (this.resolve) {
|
||||
if (this.activeElement) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
}
|
||||
this.resolve(undefined);
|
||||
}
|
||||
this.activeElement = undefined;
|
||||
super.close();
|
||||
}
|
||||
protected override onUpdateRequest(msg: Message): void {
|
||||
super.onUpdateRequest(msg);
|
||||
this.validate();
|
||||
}
|
||||
|
||||
protected validateCancellationSource = new CancellationTokenSource();
|
||||
protected async validate(): Promise<void> {
|
||||
if (!this.resolve) {
|
||||
return;
|
||||
}
|
||||
this.validateCancellationSource.cancel();
|
||||
this.validateCancellationSource = new CancellationTokenSource();
|
||||
const token = this.validateCancellationSource.token;
|
||||
const value = this.value;
|
||||
const error = await this.isValid(value, 'preview');
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
this.setErrorMessage(error);
|
||||
}
|
||||
|
||||
protected acceptCancellationSource = new CancellationTokenSource();
|
||||
protected async accept(): Promise<void> {
|
||||
if (!this.resolve) {
|
||||
return;
|
||||
}
|
||||
this.acceptCancellationSource.cancel();
|
||||
this.acceptCancellationSource = new CancellationTokenSource();
|
||||
const token = this.acceptCancellationSource.token;
|
||||
const value = this.value;
|
||||
const error = await this.isValid(value, 'open');
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
if (!DialogError.getResult(error)) {
|
||||
this.setErrorMessage(error);
|
||||
} else {
|
||||
this.resolve(value);
|
||||
Widget.detach(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract get value(): T;
|
||||
|
||||
/**
|
||||
* Return a string of zero-length or true if valid.
|
||||
*/
|
||||
protected isValid(value: T, mode: DialogMode): MaybePromise<DialogError> {
|
||||
return '';
|
||||
}
|
||||
|
||||
protected setErrorMessage(error: DialogError): void {
|
||||
if (this.acceptButton) {
|
||||
this.acceptButton.disabled = !DialogError.getResult(error);
|
||||
}
|
||||
this.errorMessageNode.innerText = DialogError.getMessage(error);
|
||||
}
|
||||
|
||||
protected addAction<K extends keyof HTMLElementEventMap>(element: HTMLElement, callback: () => void, ...additionalEventTypes: K[]): void {
|
||||
this.addKeyListener(element, Key.ENTER, callback, ...additionalEventTypes);
|
||||
}
|
||||
|
||||
protected addCloseAction<K extends keyof HTMLElementEventMap>(element: HTMLElement, ...additionalEventTypes: K[]): void {
|
||||
this.addAction(element, () => this.close(), ...additionalEventTypes);
|
||||
}
|
||||
|
||||
protected addAcceptAction<K extends keyof HTMLElementEventMap>(element: HTMLElement, ...additionalEventTypes: K[]): void {
|
||||
this.addAction(element, () => this.accept(), ...additionalEventTypes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MessageDialogProps extends DialogProps {
|
||||
readonly msg: string | HTMLElement;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ConfirmDialogProps extends MessageDialogProps {
|
||||
readonly cancel?: string;
|
||||
readonly ok?: string;
|
||||
}
|
||||
|
||||
export class ConfirmDialog extends AbstractDialog<boolean> {
|
||||
|
||||
protected confirmed = true;
|
||||
|
||||
constructor(
|
||||
@inject(ConfirmDialogProps) protected override readonly props: ConfirmDialogProps
|
||||
) {
|
||||
super(props);
|
||||
|
||||
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
|
||||
this.appendCloseButton(props.cancel);
|
||||
this.appendAcceptButton(props.ok);
|
||||
}
|
||||
|
||||
protected override onCloseRequest(msg: Message): void {
|
||||
super.onCloseRequest(msg);
|
||||
this.confirmed = false;
|
||||
this.accept();
|
||||
}
|
||||
|
||||
get value(): boolean {
|
||||
return this.confirmed;
|
||||
}
|
||||
|
||||
protected createMessageNode(msg: string | HTMLElement): HTMLElement {
|
||||
if (typeof msg === 'string') {
|
||||
const messageNode = this.node.ownerDocument.createElement('div');
|
||||
messageNode.textContent = msg;
|
||||
return messageNode;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmExit(): Promise<boolean> {
|
||||
const safeToExit = await new ConfirmDialog({
|
||||
title: nls.localizeByDefault('Are you sure you want to quit?'),
|
||||
msg: nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'),
|
||||
ok: Dialog.YES,
|
||||
cancel: Dialog.NO,
|
||||
}).open();
|
||||
return safeToExit === true;
|
||||
}
|
||||
|
||||
export class ConfirmSaveDialogProps extends MessageDialogProps {
|
||||
readonly cancel: string;
|
||||
readonly dontSave: string;
|
||||
readonly save: string;
|
||||
}
|
||||
|
||||
// Dialog prompting the user to confirm whether they wish to save changes or not
|
||||
export class ConfirmSaveDialog extends AbstractDialog<boolean | undefined> {
|
||||
protected result?: boolean = false;
|
||||
|
||||
constructor(
|
||||
@inject(ConfirmSaveDialogProps) protected override readonly props: ConfirmSaveDialogProps
|
||||
) {
|
||||
super(props);
|
||||
// Append message and buttons to the dialog
|
||||
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
|
||||
this.closeButton = this.appendButtonAndSetResult(props.cancel, false);
|
||||
this.appendButtonAndSetResult(props.dontSave, false, false);
|
||||
this.acceptButton = this.appendButtonAndSetResult(props.save, true, true);
|
||||
}
|
||||
|
||||
get value(): boolean | undefined {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
protected createMessageNode(msg: string | HTMLElement): HTMLElement {
|
||||
if (typeof msg === 'string') {
|
||||
const messageNode = document.createElement('div');
|
||||
messageNode.textContent = msg;
|
||||
return messageNode;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected appendButtonAndSetResult(text: string, primary: boolean, result?: boolean): HTMLButtonElement {
|
||||
const button = this.appendButton(text, primary);
|
||||
button.addEventListener('click', () => {
|
||||
this.result = result;
|
||||
this.accept();
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SingleTextInputDialogProps extends DialogProps {
|
||||
readonly confirmButtonLabel?: string;
|
||||
readonly initialValue?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly initialSelectionRange?: {
|
||||
start: number
|
||||
end: number
|
||||
direction?: 'forward' | 'backward' | 'none'
|
||||
};
|
||||
readonly validate?: (input: string, mode: DialogMode) => MaybePromise<DialogError>;
|
||||
}
|
||||
|
||||
export class SingleTextInputDialog extends AbstractDialog<string> {
|
||||
|
||||
protected readonly inputField: HTMLInputElement;
|
||||
|
||||
constructor(
|
||||
@inject(SingleTextInputDialogProps) protected override props: SingleTextInputDialogProps
|
||||
) {
|
||||
super(props);
|
||||
|
||||
this.inputField = document.createElement('input');
|
||||
this.inputField.type = 'text';
|
||||
this.inputField.className = 'theia-input';
|
||||
this.inputField.spellcheck = false;
|
||||
this.inputField.setAttribute('style', 'flex: 0;');
|
||||
this.inputField.placeholder = props.placeholder || '';
|
||||
this.inputField.value = props.initialValue || '';
|
||||
if (props.initialSelectionRange) {
|
||||
this.inputField.setSelectionRange(
|
||||
props.initialSelectionRange.start,
|
||||
props.initialSelectionRange.end,
|
||||
props.initialSelectionRange.direction
|
||||
);
|
||||
} else {
|
||||
this.inputField.select();
|
||||
}
|
||||
|
||||
this.contentNode.appendChild(this.inputField);
|
||||
this.controlPanel.removeChild(this.errorMessageNode);
|
||||
this.contentNode.appendChild(this.errorMessageNode);
|
||||
|
||||
this.appendAcceptButton(props.confirmButtonLabel);
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.inputField.value;
|
||||
}
|
||||
|
||||
protected override isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
|
||||
if (this.props.validate) {
|
||||
return this.props.validate(value, mode);
|
||||
}
|
||||
return super.isValid(value, mode);
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
super.onAfterAttach(msg);
|
||||
this.addUpdateListener(this.inputField, 'input');
|
||||
}
|
||||
|
||||
protected override onActivateRequest(msg: Message): void {
|
||||
this.inputField.focus();
|
||||
}
|
||||
|
||||
protected override handleEnter(event: KeyboardEvent): boolean | void {
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
return super.handleEnter(event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
47
packages/core/src/browser/dialogs/react-dialog.spec.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 Toro Cloud Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as React from 'react';
|
||||
import { enableJSDOM } from '../test/jsdom';
|
||||
|
||||
let disableJSDOM = enableJSDOM();
|
||||
|
||||
import { ReactDialog } from './react-dialog';
|
||||
|
||||
class MyDialog extends ReactDialog<void> {
|
||||
constructor() {
|
||||
super({ title: '' });
|
||||
}
|
||||
|
||||
override get value(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
protected override render(): React.ReactNode {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ReactDialog', () => {
|
||||
before(() => disableJSDOM = enableJSDOM());
|
||||
after(() => disableJSDOM());
|
||||
|
||||
it('should be extended', () => {
|
||||
const dialog = new MyDialog();
|
||||
assert.equal(dialog instanceof ReactDialog, true);
|
||||
});
|
||||
});
|
||||
57
packages/core/src/browser/dialogs/react-dialog.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from 'react';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { Disposable } from '../../common';
|
||||
import { Message } from '../widgets';
|
||||
import { AbstractDialog, DialogProps } from '../dialogs';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
@injectable()
|
||||
export abstract class ReactDialog<T> extends AbstractDialog<T> {
|
||||
protected contentNodeRoot: Root;
|
||||
protected isMounted: boolean;
|
||||
|
||||
constructor(
|
||||
@inject(DialogProps) props: DialogProps
|
||||
) {
|
||||
super(props);
|
||||
this.contentNodeRoot = createRoot(this.contentNode);
|
||||
this.isMounted = true;
|
||||
this.toDispose.push(Disposable.create(() => {
|
||||
this.contentNodeRoot.unmount();
|
||||
this.isMounted = false;
|
||||
}));
|
||||
}
|
||||
|
||||
protected override onUpdateRequest(msg: Message): void {
|
||||
super.onUpdateRequest(msg);
|
||||
if (!this.isMounted) {
|
||||
this.contentNodeRoot = createRoot(this.contentNode);
|
||||
this.isMounted = true;
|
||||
}
|
||||
this.contentNodeRoot?.render(<>{this.render()}</>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the React widget in the DOM.
|
||||
* - If the widget has been previously rendered,
|
||||
* any subsequent calls will perform an update and only
|
||||
* change the DOM if absolutely necessary.
|
||||
*/
|
||||
protected abstract render(): React.ReactNode;
|
||||
}
|
||||
117
packages/core/src/browser/diff-uris.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import URI from '../common/uri';
|
||||
import { LabelProviderContribution, LabelProvider, DidChangeLabelEvent } from './label-provider';
|
||||
import { codicon } from './widgets';
|
||||
|
||||
export namespace DiffUris {
|
||||
|
||||
export const DIFF_SCHEME = 'diff';
|
||||
|
||||
export function encode(left: URI, right: URI, label?: string): URI {
|
||||
const diffUris = [
|
||||
left.toString(),
|
||||
right.toString()
|
||||
];
|
||||
|
||||
const diffUriStr = JSON.stringify(diffUris);
|
||||
|
||||
return new URI().withScheme(DIFF_SCHEME).withPath(label || '').withQuery(diffUriStr);
|
||||
}
|
||||
|
||||
export function decode(uri: URI): URI[] {
|
||||
if (uri.scheme !== DIFF_SCHEME) {
|
||||
throw new Error((`The URI must have scheme "diff". The URI was: ${uri}.`));
|
||||
}
|
||||
const diffUris: string[] = JSON.parse(uri.query);
|
||||
return diffUris.map(s => new URI(s));
|
||||
}
|
||||
|
||||
export function isDiffUri(uri: URI): boolean {
|
||||
return uri.scheme === DIFF_SCHEME;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DiffUriLabelProviderContribution implements LabelProviderContribution {
|
||||
|
||||
constructor(@inject(LabelProvider) protected labelProvider: LabelProvider) { }
|
||||
|
||||
canHandle(element: object): number {
|
||||
if (element instanceof URI && DiffUris.isDiffUri(element)) {
|
||||
return 20;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
getLongName(uri: URI): string {
|
||||
const label = uri.path.toString();
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
const [left, right] = DiffUris.decode(uri);
|
||||
const leftLongName = this.labelProvider.getLongName(left);
|
||||
const rightLongName = this.labelProvider.getLongName(right);
|
||||
if (leftLongName === rightLongName) {
|
||||
return leftLongName;
|
||||
}
|
||||
return `${leftLongName} ⟷ ${rightLongName}`;
|
||||
}
|
||||
|
||||
getName(uri: URI): string {
|
||||
const label = uri.path.toString();
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
const [left, right] = DiffUris.decode(uri);
|
||||
|
||||
if (left.path.toString() === right.path.toString() && left.query && right.query) {
|
||||
const prefix = left.displayName ? `${left.displayName}: ` : '';
|
||||
return `${prefix}${left.query} ⟷ ${right.query}`;
|
||||
} else {
|
||||
let title;
|
||||
if (uri.displayName && left.path.toString() !== right.path.toString() && left.displayName !== uri.displayName) {
|
||||
title = `${uri.displayName}: `;
|
||||
} else {
|
||||
title = '';
|
||||
}
|
||||
|
||||
const leftLongName = this.labelProvider.getName(left);
|
||||
const rightLongName = this.labelProvider.getName(right);
|
||||
if (leftLongName === rightLongName) {
|
||||
return leftLongName;
|
||||
}
|
||||
return `${title}${leftLongName} ⟷ ${rightLongName}`;
|
||||
}
|
||||
}
|
||||
|
||||
getIcon(uri: URI): string {
|
||||
return codicon('split-horizontal');
|
||||
}
|
||||
|
||||
affects(diffUri: URI, event: DidChangeLabelEvent): boolean {
|
||||
for (const uri of DiffUris.decode(diffUri)) {
|
||||
if (event.affects(uri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
97
packages/core/src/browser/encoding-registry.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// based on https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/workbench/services/textfile/browser/textFileService.ts#L491
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import URI from '../common/uri';
|
||||
import { Disposable } from '../common/disposable';
|
||||
import { EncodingService as EncodingService } from '../common/encoding-service';
|
||||
import { UTF8 } from '../common/encodings';
|
||||
import { CorePreferences } from '../common/core-preferences';
|
||||
|
||||
export interface EncodingOverride {
|
||||
parent?: URI;
|
||||
extension?: string;
|
||||
scheme?: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class EncodingRegistry {
|
||||
|
||||
protected readonly encodingOverrides: EncodingOverride[] = [];
|
||||
|
||||
@inject(CorePreferences)
|
||||
protected readonly preferences: CorePreferences;
|
||||
|
||||
@inject(EncodingService)
|
||||
protected readonly encodingService: EncodingService;
|
||||
|
||||
registerOverride(override: EncodingOverride): Disposable {
|
||||
this.encodingOverrides.push(override);
|
||||
return Disposable.create(() => {
|
||||
const index = this.encodingOverrides.indexOf(override);
|
||||
if (index !== -1) {
|
||||
this.encodingOverrides.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getEncodingForResource(resource: URI, preferredEncoding?: string): string {
|
||||
let fileEncoding: string;
|
||||
|
||||
const override = this.getEncodingOverride(resource);
|
||||
if (override) {
|
||||
fileEncoding = override; // encoding override always wins
|
||||
} else if (preferredEncoding) {
|
||||
fileEncoding = preferredEncoding; // preferred encoding comes second
|
||||
} else {
|
||||
fileEncoding = this.preferences.get('files.encoding', undefined, resource.toString());
|
||||
}
|
||||
|
||||
if (!fileEncoding || !this.encodingService.exists(fileEncoding)) {
|
||||
return UTF8; // the default is UTF 8
|
||||
}
|
||||
|
||||
return this.encodingService.toIconvEncoding(fileEncoding);
|
||||
}
|
||||
|
||||
protected getEncodingOverride(resource: URI): string | undefined {
|
||||
if (this.encodingOverrides && this.encodingOverrides.length) {
|
||||
for (const override of this.encodingOverrides) {
|
||||
if (override.parent && resource.isEqualOrParent(override.parent)) {
|
||||
return override.encoding;
|
||||
}
|
||||
|
||||
if (override.extension && resource.path.ext === `.${override.extension}`) {
|
||||
return override.encoding;
|
||||
}
|
||||
|
||||
if (override.scheme && override.scheme === resource.scheme) {
|
||||
return override.encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
148
packages/core/src/browser/endpoint.spec.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as chai from 'chai';
|
||||
import { Endpoint } from './endpoint';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Endpoint', () => {
|
||||
|
||||
describe('01 #getWebSocketUrl', () => {
|
||||
|
||||
it('Should correctly join root pathname', () => {
|
||||
expectWsUri(
|
||||
{
|
||||
httpScheme: 'ws',
|
||||
path: '/miau/'
|
||||
},
|
||||
{
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: ''
|
||||
}, 'ws://example.org/miau/');
|
||||
});
|
||||
|
||||
it('Should correctly join pathname and path', () => {
|
||||
expectWsUri(
|
||||
{
|
||||
httpScheme: 'ws',
|
||||
path: '/miau/'
|
||||
},
|
||||
{
|
||||
host: 'example.org',
|
||||
pathname: '/mainresource',
|
||||
search: '',
|
||||
protocol: ''
|
||||
}, 'ws://example.org/mainresource/miau/');
|
||||
});
|
||||
|
||||
it('Should correctly join pathname and path, ignoring double slash in between', () => {
|
||||
expectWsUri(
|
||||
{
|
||||
httpScheme: 'ws',
|
||||
path: '/miau/'
|
||||
},
|
||||
{
|
||||
host: 'example.org',
|
||||
pathname: '/mainresource/',
|
||||
search: '',
|
||||
protocol: ''
|
||||
}, 'ws://example.org/mainresource/miau/');
|
||||
});
|
||||
|
||||
it('Should correctly join pathname and path, without trailing slash', () => {
|
||||
expectWsUri(
|
||||
{
|
||||
httpScheme: 'ws',
|
||||
path: '/miau'
|
||||
},
|
||||
{
|
||||
host: 'example.org',
|
||||
pathname: '/mainresource',
|
||||
search: '',
|
||||
protocol: ''
|
||||
}, 'ws://example.org/mainresource/miau');
|
||||
});
|
||||
});
|
||||
|
||||
describe('02 #httpScheme', () => {
|
||||
|
||||
it('Should choose https:// if location protocol is https://', () => {
|
||||
expectRestUri(
|
||||
{
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: 'https:'
|
||||
}, 'https://example.org/');
|
||||
});
|
||||
|
||||
it("should return with the 'options.httpScheme' if defined", () => {
|
||||
expect(new Endpoint({ httpScheme: 'foo:' }, {
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: 'https:'
|
||||
}).httpScheme).to.be.equal('foo:');
|
||||
});
|
||||
|
||||
it('should return with the HTTP if the protocol is HTTP.', () => {
|
||||
expect(new Endpoint({}, {
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: 'http:'
|
||||
}).httpScheme).to.be.equal('http:');
|
||||
});
|
||||
|
||||
it('should return with the HTTPS if the protocol is HTTPS.', () => {
|
||||
expect(new Endpoint({}, {
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: 'https:'
|
||||
}).httpScheme).to.be.equal('https:');
|
||||
});
|
||||
|
||||
it('should return with the HTTP if the protocol is *not* HTTP or HTTPS.', () => {
|
||||
expect(new Endpoint({}, {
|
||||
host: 'example.org',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
protocol: 'file:'
|
||||
}).httpScheme).to.be.equal('http:');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function expectWsUri(options: Endpoint.Options, mockLocation: Endpoint.Location, expectedUri: string): void {
|
||||
const cut = new Endpoint(options, mockLocation);
|
||||
const uri = cut.getWebSocketUrl();
|
||||
expect(uri.toString()).to.eq(expectedUri);
|
||||
}
|
||||
|
||||
function expectRestUri(options: Endpoint.Options, mockLocation: Endpoint.Location, expectedUri: string): void {
|
||||
const cut = new Endpoint(options, mockLocation);
|
||||
const uri = cut.getRestUrl();
|
||||
expect(uri.toString()).to.eq(expectedUri);
|
||||
}
|
||||
136
packages/core/src/browser/endpoint.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import URI from '../common/uri';
|
||||
|
||||
/**
|
||||
* An endpoint provides URLs for http and ws, based on configuration and defaults.
|
||||
*/
|
||||
export class Endpoint {
|
||||
static readonly PROTO_HTTPS: string = 'https:';
|
||||
static readonly PROTO_HTTP: string = 'http:';
|
||||
static readonly PROTO_WS: string = 'ws:';
|
||||
static readonly PROTO_WSS: string = 'wss:';
|
||||
static readonly PROTO_FILE: string = 'file:';
|
||||
|
||||
constructor(
|
||||
protected readonly options: Endpoint.Options = {},
|
||||
protected readonly location: Endpoint.Location = self.location
|
||||
) { }
|
||||
|
||||
getWebSocketUrl(): URI {
|
||||
return new URI(`${this.wsScheme}//${this.host}${this.pathname}${this.path}`);
|
||||
}
|
||||
|
||||
getRestUrl(): URI {
|
||||
return new URI(`${this.httpScheme}//${this.host}${this.pathname}${this.path}`);
|
||||
}
|
||||
|
||||
protected get pathname(): string {
|
||||
if (this.location.protocol === Endpoint.PROTO_FILE) {
|
||||
return '';
|
||||
}
|
||||
if (this.location.pathname === '/') {
|
||||
return '';
|
||||
}
|
||||
if (this.location.pathname.endsWith('/')) {
|
||||
return this.location.pathname.substring(0, this.location.pathname.length - 1);
|
||||
}
|
||||
return this.location.pathname;
|
||||
}
|
||||
|
||||
get host(): string {
|
||||
if (this.options.host) {
|
||||
return this.options.host;
|
||||
}
|
||||
if (this.location.host) {
|
||||
return this.location.host;
|
||||
}
|
||||
return 'localhost:' + this.port;
|
||||
}
|
||||
|
||||
get origin(): string {
|
||||
return `${this.httpScheme}//${this.host}`;
|
||||
}
|
||||
|
||||
protected get port(): string {
|
||||
return this.getSearchParam('port', '3000');
|
||||
}
|
||||
|
||||
protected getSearchParam(name: string, defaultValue: string): string {
|
||||
const search = this.location.search;
|
||||
if (!search) {
|
||||
return defaultValue;
|
||||
}
|
||||
return search.substring(1).split('&')
|
||||
.filter(value => value.startsWith(name + '='))
|
||||
.map(value => {
|
||||
const encoded = value.substring(name.length + 1);
|
||||
return decodeURIComponent(encoded);
|
||||
})[0] || defaultValue;
|
||||
}
|
||||
|
||||
protected get wsScheme(): string {
|
||||
if (this.options.wsScheme) {
|
||||
return this.options.wsScheme;
|
||||
}
|
||||
return this.httpScheme === Endpoint.PROTO_HTTPS ? Endpoint.PROTO_WSS : Endpoint.PROTO_WS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTTP/HTTPS scheme of the endpoint, or the user defined one.
|
||||
* See: `Endpoint.Options.httpScheme`.
|
||||
*/
|
||||
get httpScheme(): string {
|
||||
if (this.options.httpScheme) {
|
||||
return this.options.httpScheme;
|
||||
}
|
||||
if (this.location.protocol === Endpoint.PROTO_HTTP ||
|
||||
this.location.protocol === Endpoint.PROTO_HTTPS) {
|
||||
return this.location.protocol;
|
||||
}
|
||||
return Endpoint.PROTO_HTTP;
|
||||
}
|
||||
|
||||
protected get path(): string {
|
||||
if (this.options.path) {
|
||||
if (this.options.path.startsWith('/')) {
|
||||
return this.options.path;
|
||||
} else {
|
||||
return '/' + this.options.path;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Endpoint {
|
||||
export class Options {
|
||||
host?: string;
|
||||
wsScheme?: string;
|
||||
httpScheme?: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
// Necessary for running tests with dependency on TS lib on node
|
||||
// FIXME figure out how to mock with ts-node
|
||||
export class Location {
|
||||
host: string;
|
||||
pathname: string;
|
||||
search: string;
|
||||
protocol: string;
|
||||
}
|
||||
}
|
||||
79
packages/core/src/browser/external-uri-service.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { environment } from '@theia/application-package/lib/environment';
|
||||
import { injectable } from 'inversify';
|
||||
import { MaybePromise } from '../common/types';
|
||||
import URI from '../common/uri';
|
||||
import { Endpoint } from './endpoint';
|
||||
|
||||
export interface AddressPort {
|
||||
address: string
|
||||
port: number
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ExternalUriService {
|
||||
|
||||
/**
|
||||
* Maps local to remote URLs.
|
||||
* Should be no-op if the given URL is not a localhost URL.
|
||||
*
|
||||
* By default maps to an origin serving Theia.
|
||||
*
|
||||
* Use `parseLocalhost` to retrieve localhost address and port information.
|
||||
*/
|
||||
resolve(uri: URI): MaybePromise<URI> {
|
||||
const address = this.parseLocalhost(uri);
|
||||
if (address) {
|
||||
return this.toRemoteUrl(uri, address);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
parseLocalhost(uri: URI): AddressPort | undefined {
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
return;
|
||||
}
|
||||
const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority);
|
||||
if (!localhostMatch) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
address: localhostMatch[1],
|
||||
port: +localhostMatch[2],
|
||||
};
|
||||
}
|
||||
|
||||
protected toRemoteUrl(uri: URI, address: AddressPort): URI {
|
||||
return new Endpoint({ host: this.toRemoteHost(address) })
|
||||
.getRestUrl()
|
||||
.withPath(uri.path)
|
||||
.withFragment(uri.fragment)
|
||||
.withQuery(uri.query);
|
||||
}
|
||||
|
||||
protected toRemoteHost(address: AddressPort): string {
|
||||
return `${this.getRemoteHost()}:${address.port}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The remote host (where the backend is running).
|
||||
*/
|
||||
protected getRemoteHost(): string {
|
||||
return environment.electron.is() ? 'localhost' : window.location.hostname;
|
||||
}
|
||||
}
|
||||
20
packages/core/src/browser/file-icons-js.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
declare module 'file-icons-js' {
|
||||
function getClass(filePath: string): string;
|
||||
function getClassWithColor(filePath: string): string;
|
||||
}
|
||||
57
packages/core/src/browser/frontend-application-bindings.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { interfaces } from 'inversify';
|
||||
import {
|
||||
bindContributionProvider, DefaultResourceProvider, MaybePromise, MessageClient,
|
||||
MessageService, ResourceProvider, ResourceResolver
|
||||
} from '../common';
|
||||
import { PreferenceProvider } from '../common/preferences/preference-provider';
|
||||
import {
|
||||
bindPreferenceSchemaProvider,
|
||||
PreferenceValidationService
|
||||
} from './preferences';
|
||||
import {
|
||||
InjectablePreferenceProxy, PreferenceProviderProvider, PreferenceProxyFactory,
|
||||
PreferenceProxyOptions, PreferenceProxySchema, PreferenceSchema, PreferenceScope, PreferenceService, PreferenceServiceImpl
|
||||
} from '../common/preferences';
|
||||
|
||||
export function bindMessageService(bind: interfaces.Bind): interfaces.BindingWhenOnSyntax<MessageService> {
|
||||
bind(MessageClient).toSelf().inSingletonScope();
|
||||
return bind(MessageService).toSelf().inSingletonScope();
|
||||
}
|
||||
|
||||
export function bindPreferenceService(bind: interfaces.Bind): void {
|
||||
bind(PreferenceProviderProvider).toFactory(ctx => (scope: PreferenceScope) => ctx.container.getNamed(PreferenceProvider, scope));
|
||||
bind(PreferenceServiceImpl).toSelf().inSingletonScope();
|
||||
bind(PreferenceService).toService(PreferenceServiceImpl);
|
||||
bindPreferenceSchemaProvider(bind);
|
||||
bind(PreferenceValidationService).toSelf().inSingletonScope();
|
||||
bind(InjectablePreferenceProxy).toSelf();
|
||||
bind(PreferenceProxyFactory).toFactory(({ container }) => (schema: MaybePromise<PreferenceSchema>, options: PreferenceProxyOptions = {}) => {
|
||||
const child = container.createChild();
|
||||
child.bind(PreferenceProxyOptions).toConstantValue(options ?? {});
|
||||
child.bind(PreferenceProxySchema).toConstantValue(() => schema);
|
||||
const handler = child.get(InjectablePreferenceProxy);
|
||||
return new Proxy(Object.create(null), handler); // eslint-disable-line no-null/no-null
|
||||
});
|
||||
}
|
||||
|
||||
export function bindResourceProvider(bind: interfaces.Bind): void {
|
||||
bind(DefaultResourceProvider).toSelf().inSingletonScope();
|
||||
bind(ResourceProvider).toProvider(context => uri => context.container.get(DefaultResourceProvider).get(uri));
|
||||
bindContributionProvider(bind, ResourceResolver);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { enableJSDOM } from '../browser/test/jsdom';
|
||||
let disableJSDOM = enableJSDOM();
|
||||
|
||||
import { FrontendApplicationConfig } from '@theia/application-package/lib/';
|
||||
import { expect } from 'chai';
|
||||
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
|
||||
|
||||
disableJSDOM();
|
||||
|
||||
const { DEFAULT } = FrontendApplicationConfig;
|
||||
|
||||
describe('FrontendApplicationConfigProvider', function (): void {
|
||||
|
||||
before(() => disableJSDOM = enableJSDOM());
|
||||
after(() => disableJSDOM());
|
||||
|
||||
it('should use defaults when calling `set`', function (): void {
|
||||
FrontendApplicationConfigProvider.set({
|
||||
applicationName: DEFAULT.applicationName + ' Something Else',
|
||||
});
|
||||
const config = FrontendApplicationConfigProvider.get();
|
||||
// custom values
|
||||
expect(config.applicationName).not.equal(DEFAULT.applicationName);
|
||||
// defaults
|
||||
expect(config.defaultIconTheme).equal(DEFAULT.defaultIconTheme);
|
||||
expect(config.defaultTheme).deep.equal(DEFAULT.defaultTheme);
|
||||
expect(config.electron.windowOptions).deep.equal(DEFAULT.electron.windowOptions);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { FrontendApplicationConfig, deepmerge } from '@theia/application-package/lib/application-props';
|
||||
|
||||
export const DEFAULT_BACKGROUND_COLOR_STORAGE_KEY = 'theme.background';
|
||||
|
||||
export class FrontendApplicationConfigProvider {
|
||||
|
||||
private static KEY = Symbol('FrontendApplicationConfigProvider');
|
||||
|
||||
static get(): FrontendApplicationConfig {
|
||||
const config = FrontendApplicationConfigProvider.doGet();
|
||||
if (config === undefined) {
|
||||
throw new Error('The configuration is not set. Did you call FrontendApplicationConfigProvider#set?');
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
static set(config: FrontendApplicationConfig.Partial): void {
|
||||
if (FrontendApplicationConfigProvider.doGet() !== undefined) {
|
||||
throw new Error('The configuration is already set.');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const globalObject = window as any;
|
||||
const key = FrontendApplicationConfigProvider.KEY;
|
||||
globalObject[key] = deepmerge(FrontendApplicationConfig.DEFAULT, config);
|
||||
}
|
||||
|
||||
private static doGet(): FrontendApplicationConfig | undefined {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const globalObject = window as any;
|
||||
const key = FrontendApplicationConfigProvider.KEY;
|
||||
return globalObject[key];
|
||||
}
|
||||
|
||||
}
|
||||
110
packages/core/src/browser/frontend-application-contribution.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import type { FrontendApplication } from './frontend-application';
|
||||
import { MaybePromise, isObject } from '../common/types';
|
||||
import { StopReason } from '../common/frontend-application-state';
|
||||
import { injectable } from 'inversify';
|
||||
|
||||
/**
|
||||
* Clients can implement to get a callback for contributing widgets to a shell on start.
|
||||
*/
|
||||
export const FrontendApplicationContribution = Symbol('FrontendApplicationContribution');
|
||||
export interface FrontendApplicationContribution {
|
||||
|
||||
/**
|
||||
* Called on application startup before configure is called.
|
||||
*/
|
||||
initialize?(): void;
|
||||
|
||||
/**
|
||||
* Called before commands, key bindings and menus are initialized.
|
||||
* Should return a promise if it runs asynchronously.
|
||||
*/
|
||||
configure?(app: FrontendApplication): MaybePromise<void>;
|
||||
|
||||
/**
|
||||
* Called when the application is started. The application shell is not attached yet when this method runs.
|
||||
* Should return a promise if it runs asynchronously.
|
||||
*/
|
||||
onStart?(app: FrontendApplication): MaybePromise<void>;
|
||||
|
||||
/**
|
||||
* Called on `beforeunload` event, right before the window closes.
|
||||
* Return `true` or an OnWillStopAction in order to prevent exit.
|
||||
* Note: No async code allowed, this function has to run on one tick.
|
||||
*/
|
||||
onWillStop?(app: FrontendApplication): boolean | undefined | OnWillStopAction<unknown>;
|
||||
|
||||
/**
|
||||
* Called when an application is stopped or unloaded.
|
||||
*
|
||||
* Note that this is implemented using `window.beforeunload` which doesn't allow any asynchronous code anymore.
|
||||
* I.e. this is the last tick.
|
||||
*/
|
||||
onStop?(app: FrontendApplication): void;
|
||||
|
||||
/**
|
||||
* Called after the application shell has been attached in case there is no previous workbench layout state.
|
||||
* Should return a promise if it runs asynchronously.
|
||||
*/
|
||||
initializeLayout?(app: FrontendApplication): MaybePromise<void>;
|
||||
|
||||
/**
|
||||
* An event is emitted when a layout is initialized, but before the shell is attached.
|
||||
*/
|
||||
onDidInitializeLayout?(app: FrontendApplication): MaybePromise<void>;
|
||||
}
|
||||
|
||||
export interface OnWillStopAction<T = unknown> {
|
||||
/**
|
||||
* @resolves to a prepared value to be passed into the `action` function.
|
||||
*/
|
||||
prepare?: (stopReason?: StopReason) => MaybePromise<T>;
|
||||
/**
|
||||
* @resolves to `true` if it is safe to close the application; `false` otherwise.
|
||||
*/
|
||||
action: (prepared: T, stopReason?: StopReason) => MaybePromise<boolean>;
|
||||
/**
|
||||
* A descriptive string for the reason preventing close.
|
||||
*/
|
||||
reason: string;
|
||||
/**
|
||||
* A number representing priority. Higher priority items are run later.
|
||||
* High priority implies that some options of this check will have negative impacts if
|
||||
* the user subsequently cancels the shutdown.
|
||||
*/
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export namespace OnWillStopAction {
|
||||
export function is(candidate: unknown): candidate is OnWillStopAction {
|
||||
return isObject(candidate) && 'action' in candidate && 'reason' in candidate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default frontend contribution that can be extended by clients if they do not want to implement any of the
|
||||
* methods from the interface but still want to contribute to the frontend application.
|
||||
*/
|
||||
@injectable()
|
||||
export abstract class DefaultFrontendApplicationContribution implements FrontendApplicationContribution {
|
||||
|
||||
initialize(): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
486
packages/core/src/browser/frontend-application-module.ts
Normal file
@@ -0,0 +1,486 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import '../../src/browser/style/index.css';
|
||||
require('../../src/browser/style/materialcolors.css').use();
|
||||
import 'font-awesome/css/font-awesome.min.css';
|
||||
import 'file-icons-js/css/style.css';
|
||||
import '@vscode/codicons/dist/codicon.css';
|
||||
|
||||
import { ContainerModule } from 'inversify';
|
||||
import {
|
||||
bindContributionProvider,
|
||||
SelectionService,
|
||||
ResourceResolver,
|
||||
CommandContribution, CommandRegistry, CommandService, commandServicePath,
|
||||
MenuModelRegistry, MenuContribution,
|
||||
MessageClient,
|
||||
InMemoryResources,
|
||||
messageServicePath,
|
||||
InMemoryTextResourceResolver,
|
||||
UntitledResourceResolver,
|
||||
MenuPath,
|
||||
PreferenceService
|
||||
} from '../common';
|
||||
import { KeybindingRegistry, KeybindingContext, KeybindingContribution } from './keybinding';
|
||||
import { FrontendApplication } from './frontend-application';
|
||||
import { FrontendApplicationContribution, DefaultFrontendApplicationContribution } from './frontend-application-contribution';
|
||||
import { DefaultOpenerService, OpenerService, OpenHandler } from './opener-service';
|
||||
import { HttpOpenHandler } from './http-open-handler';
|
||||
import { CommonFrontendContribution } from './common-frontend-contribution';
|
||||
import { LocalStorageService, StorageService } from './storage-service';
|
||||
import { WidgetFactory, WidgetManager } from './widget-manager';
|
||||
import {
|
||||
ApplicationShell, ApplicationShellOptions, DockPanelRenderer, TabBarRenderer,
|
||||
TabBarRendererFactory, ShellLayoutRestorer,
|
||||
SidePanelHandler, SidePanelHandlerFactory,
|
||||
SidebarMenuWidget, SidebarTopMenuWidgetFactory,
|
||||
SplitPositionHandler, DockPanelRendererFactory, ApplicationShellLayoutMigration, ApplicationShellLayoutMigrationError, SidebarBottomMenuWidgetFactory,
|
||||
ShellLayoutTransformer
|
||||
} from './shell';
|
||||
import { LabelParser } from './label-parser';
|
||||
import { LabelProvider, LabelProviderContribution, DefaultUriLabelProviderContribution } from './label-provider';
|
||||
import { ContextMenuRenderer, Coordinate } from './context-menu-renderer';
|
||||
import { ThemeService } from './theming';
|
||||
import { ConnectionStatusService, FrontendConnectionStatusService, ApplicationConnectionStatusContribution, PingService } from './connection-status-service';
|
||||
import { DiffUriLabelProviderContribution } from './diff-uris';
|
||||
import { ApplicationServer, applicationPath } from '../common/application-protocol';
|
||||
import { WebSocketConnectionProvider } from './messaging';
|
||||
import { AboutDialog, AboutDialogProps } from './about-dialog';
|
||||
import { EnvVariablesServer, envVariablesPath, EnvVariable } from './../common/env-variables';
|
||||
import { FrontendApplicationStateService } from './frontend-application-state';
|
||||
import { JsonSchemaStore, JsonSchemaContribution, DefaultJsonSchemaContribution, JsonSchemaDataStore } from './json-schema-store';
|
||||
import { TabBarToolbarRegistry, TabBarToolbarContribution, TabBarToolbarFactory, TabBarToolbar } from './shell/tab-bar-toolbar';
|
||||
import { ContextKeyService, ContextKeyServiceDummyImpl } from './context-key-service';
|
||||
import { ResourceContextKey } from './resource-context-key';
|
||||
import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
|
||||
import { MimeService } from './mime-service';
|
||||
import { ApplicationShellMouseTracker } from './shell/application-shell-mouse-tracker';
|
||||
import { ViewContainer, ViewContainerIdentifier } from './view-container';
|
||||
import { QuickViewService } from './quick-input/quick-view-service';
|
||||
import { DialogOverlayService } from './dialogs';
|
||||
import { ProgressLocationService } from './progress-location-service';
|
||||
import { ProgressClient } from '../common/progress-service-protocol';
|
||||
import { ProgressService } from '../common/progress-service';
|
||||
import { DispatchingProgressClient } from './progress-client';
|
||||
import { ProgressStatusBarItem } from './progress-status-bar-item';
|
||||
import { TabBarDecoratorService, TabBarDecorator } from './shell/tab-bar-decorator';
|
||||
import { ContextMenuContext } from './menu/context-menu-context';
|
||||
import { bindResourceProvider, bindMessageService, bindPreferenceService } from './frontend-application-bindings';
|
||||
import { ColorRegistry } from './color-registry';
|
||||
import { ColorContribution, ColorApplicationContribution } from './color-application-contribution';
|
||||
import { ExternalUriService } from './external-uri-service';
|
||||
import { IconThemeService, NoneIconTheme } from './icon-theme-service';
|
||||
import { IconThemeApplicationContribution, IconThemeContribution, DefaultFileIconThemeContribution } from './icon-theme-contribution';
|
||||
import { TreeLabelProvider } from './tree/tree-label-provider';
|
||||
import { ProgressBar } from './progress-bar';
|
||||
import { ProgressBarFactory, ProgressBarOptions } from './progress-bar-factory';
|
||||
import { CommandOpenHandler } from './command-open-handler';
|
||||
import { LanguageService } from './language-service';
|
||||
import { EncodingRegistry } from './encoding-registry';
|
||||
import { EncodingService } from '../common/encoding-service';
|
||||
import { AuthenticationService, AuthenticationServiceImpl } from '../browser/authentication-service';
|
||||
import { DecorationsService, DecorationsServiceImpl } from './decorations-service';
|
||||
import { keyStoreServicePath, KeyStoreService } from '../common/key-store';
|
||||
import { CredentialsService, CredentialsServiceImpl } from './credentials-service';
|
||||
import { ContributionFilterRegistry, ContributionFilterRegistryImpl } from '../common/contribution-filter';
|
||||
import { QuickCommandFrontendContribution } from './quick-input/quick-command-frontend-contribution';
|
||||
import { QuickPickService, quickPickServicePath } from '../common/quick-pick-service';
|
||||
import {
|
||||
QuickPickServiceImpl,
|
||||
QuickInputFrontendContribution,
|
||||
QuickAccessContribution,
|
||||
QuickCommandService,
|
||||
QuickHelpService
|
||||
} from './quick-input';
|
||||
import { SidebarBottomMenuWidget } from './shell/sidebar-bottom-menu-widget';
|
||||
import { WindowContribution } from './window-contribution';
|
||||
import {
|
||||
BreadcrumbID,
|
||||
BreadcrumbPopupContainer,
|
||||
BreadcrumbPopupContainerFactory,
|
||||
BreadcrumbRenderer,
|
||||
BreadcrumbsContribution,
|
||||
BreadcrumbsRenderer,
|
||||
BreadcrumbsRendererFactory,
|
||||
BreadcrumbsService,
|
||||
DefaultBreadcrumbRenderer,
|
||||
} from './breadcrumbs';
|
||||
import { DockPanel, RendererHost } from './widgets';
|
||||
import { TooltipService, TooltipServiceImpl } from './tooltip-service';
|
||||
import { BackendRequestService, RequestService, REQUEST_SERVICE_PATH } from '@theia/request';
|
||||
import { bindFrontendStopwatch, bindBackendStopwatch } from './performance';
|
||||
import { SaveableService } from './saveable-service';
|
||||
import { SecondaryWindowHandler } from './secondary-window-handler';
|
||||
import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
|
||||
import { WindowTitleService } from './window/window-title-service';
|
||||
import { WindowTitleUpdater } from './window/window-title-updater';
|
||||
import { TheiaDockPanel } from './shell/theia-dock-panel';
|
||||
import { bindStatusBar } from './status-bar';
|
||||
import { MarkdownRenderer, MarkdownRendererFactory, MarkdownRendererImpl } from './markdown-rendering/markdown-renderer';
|
||||
import { StylingParticipant, StylingService } from './styling-service';
|
||||
import { bindCommonStylingParticipants } from './common-styling-participants';
|
||||
import { HoverService } from './hover-service';
|
||||
import { AdditionalViewsMenuPath, AdditionalViewsMenuWidget, AdditionalViewsMenuWidgetFactory } from './shell/additional-views-menu-widget';
|
||||
import { LanguageIconLabelProvider } from './language-icon-provider';
|
||||
import { bindTreePreferences } from '../common/tree-preference';
|
||||
import { OpenWithService } from './open-with-service';
|
||||
import { ViewColumnService } from './shell/view-column-service';
|
||||
import { DomInputUndoRedoHandler, UndoRedoHandler, UndoRedoHandlerService } from './undo-redo-handler';
|
||||
import { WidgetStatusBarContribution, WidgetStatusBarService } from './widget-status-bar-service';
|
||||
import { SymbolIconColorContribution } from './symbol-icon-color-contribution';
|
||||
import { CorePreferences, bindCorePreferences } from '../common/core-preferences';
|
||||
import { bindBadgeDecoration } from './badges';
|
||||
|
||||
export { bindResourceProvider, bindMessageService, bindPreferenceService };
|
||||
|
||||
export const frontendApplicationModule = new ContainerModule((bind, _unbind, _isBound, _rebind) => {
|
||||
bind(NoneIconTheme).toSelf().inSingletonScope();
|
||||
bind(LabelProviderContribution).toService(NoneIconTheme);
|
||||
bind(IconThemeService).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, IconThemeContribution);
|
||||
bind(DefaultFileIconThemeContribution).toSelf().inSingletonScope();
|
||||
bind(IconThemeContribution).toService(DefaultFileIconThemeContribution);
|
||||
bind(IconThemeApplicationContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(IconThemeApplicationContribution);
|
||||
bind(LanguageIconLabelProvider).toSelf().inSingletonScope();
|
||||
bind(LabelProviderContribution).toService(LanguageIconLabelProvider);
|
||||
|
||||
bind(ColorRegistry).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, ColorContribution);
|
||||
bind(ColorApplicationContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(ColorApplicationContribution);
|
||||
|
||||
bind(FrontendApplication).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationStateService).toSelf().inSingletonScope();
|
||||
bind(DefaultFrontendApplicationContribution).toSelf();
|
||||
bindContributionProvider(bind, FrontendApplicationContribution);
|
||||
|
||||
bind(ApplicationShellOptions).toConstantValue({});
|
||||
bind(ApplicationShell).toSelf().inSingletonScope();
|
||||
bind(SidePanelHandlerFactory).toAutoFactory(SidePanelHandler);
|
||||
bind(SidePanelHandler).toSelf();
|
||||
bind(SidebarTopMenuWidgetFactory).toAutoFactory(SidebarMenuWidget);
|
||||
bind(SidebarMenuWidget).toSelf();
|
||||
bind(SidebarBottomMenuWidget).toSelf();
|
||||
bind(SidebarBottomMenuWidgetFactory).toAutoFactory(SidebarBottomMenuWidget);
|
||||
bind(AdditionalViewsMenuWidget).toSelf();
|
||||
bind(AdditionalViewsMenuWidgetFactory).toFactory(ctx => (side: 'left' | 'right') => {
|
||||
const childContainer = ctx.container.createChild();
|
||||
childContainer.bind<MenuPath>(AdditionalViewsMenuPath).toConstantValue(['additional_views_menu', side]);
|
||||
return childContainer.resolve(AdditionalViewsMenuWidget);
|
||||
});
|
||||
bind(SplitPositionHandler).toSelf().inSingletonScope();
|
||||
|
||||
bindContributionProvider(bind, TabBarToolbarContribution);
|
||||
bind(TabBarToolbarRegistry).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(TabBarToolbarRegistry);
|
||||
bind(TabBarToolbarFactory).toFactory(context => () => {
|
||||
const container = context.container.createChild();
|
||||
container.bind(TabBarToolbar).toSelf().inSingletonScope();
|
||||
return container.get(TabBarToolbar);
|
||||
});
|
||||
|
||||
bind(DockPanelRendererFactory).toFactory<DockPanelRenderer, [(Document | ShadowRoot)?]>(context => (document?: Document | ShadowRoot) => {
|
||||
const renderer = context.container.get(DockPanelRenderer);
|
||||
renderer.document = document;
|
||||
return renderer;
|
||||
});
|
||||
bind(DockPanelRenderer).toSelf();
|
||||
bind(TabBarRendererFactory).toFactory(({ container }) => () => {
|
||||
const contextMenuRenderer = container.get(ContextMenuRenderer);
|
||||
const tabBarDecoratorService = container.get(TabBarDecoratorService);
|
||||
const iconThemeService = container.get(IconThemeService);
|
||||
const selectionService = container.get(SelectionService);
|
||||
const commandService = container.get<CommandService>(CommandService);
|
||||
const corePreferences = container.get<CorePreferences>(CorePreferences);
|
||||
const hoverService = container.get(HoverService);
|
||||
const contextKeyService: ContextKeyService = container.get(ContextKeyService);
|
||||
return new TabBarRenderer(contextMenuRenderer, tabBarDecoratorService, iconThemeService,
|
||||
selectionService, commandService, corePreferences, hoverService, contextKeyService);
|
||||
});
|
||||
bind(TheiaDockPanel.Factory).toFactory(({ container }) => (options?: DockPanel.IOptions, maximizeCallback?: (area: TheiaDockPanel) => void) => {
|
||||
const corePreferences = container.get<CorePreferences>(CorePreferences);
|
||||
return new TheiaDockPanel(options, corePreferences, maximizeCallback);
|
||||
});
|
||||
|
||||
bindContributionProvider(bind, TabBarDecorator);
|
||||
bind(TabBarDecoratorService).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(TabBarDecoratorService);
|
||||
|
||||
bindContributionProvider(bind, OpenHandler);
|
||||
bind(DefaultOpenerService).toSelf().inSingletonScope();
|
||||
bind(OpenerService).toService(DefaultOpenerService);
|
||||
|
||||
bind(ExternalUriService).toSelf().inSingletonScope();
|
||||
bind(HttpOpenHandler).toSelf().inSingletonScope();
|
||||
bind(OpenHandler).toService(HttpOpenHandler);
|
||||
|
||||
bind(CommandOpenHandler).toSelf().inSingletonScope();
|
||||
bind(OpenHandler).toService(CommandOpenHandler);
|
||||
|
||||
bind(OpenWithService).toSelf().inSingletonScope();
|
||||
|
||||
bind(TooltipServiceImpl).toSelf().inSingletonScope();
|
||||
bind(TooltipService).toService(TooltipServiceImpl);
|
||||
|
||||
bindContributionProvider(bind, ApplicationShellLayoutMigration);
|
||||
bind<ApplicationShellLayoutMigration>(ApplicationShellLayoutMigration).toConstantValue({
|
||||
layoutVersion: 2.0,
|
||||
onWillInflateLayout({ layoutVersion }): void {
|
||||
throw ApplicationShellLayoutMigrationError.create(
|
||||
`It is not possible to migrate layout of version ${layoutVersion} to version ${this.layoutVersion}.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
bindContributionProvider(bind, ShellLayoutTransformer);
|
||||
|
||||
bindContributionProvider(bind, WidgetFactory);
|
||||
bind(WidgetManager).toSelf().inSingletonScope();
|
||||
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(ShellLayoutRestorer);
|
||||
|
||||
bindResourceProvider(bind);
|
||||
bind(InMemoryResources).toSelf().inSingletonScope();
|
||||
bind(ResourceResolver).toService(InMemoryResources);
|
||||
|
||||
bind(InMemoryTextResourceResolver).toSelf().inSingletonScope();
|
||||
bind(ResourceResolver).toService(InMemoryTextResourceResolver);
|
||||
|
||||
bind(UntitledResourceResolver).toSelf().inSingletonScope();
|
||||
bind(ResourceResolver).toService(UntitledResourceResolver);
|
||||
|
||||
bind(SelectionService).toSelf().inSingletonScope();
|
||||
bind(CommandRegistry).toSelf().inSingletonScope().onActivation(({ container }, registry) => {
|
||||
WebSocketConnectionProvider.createHandler(container, commandServicePath, registry);
|
||||
return registry;
|
||||
});
|
||||
bind(CommandService).toService(CommandRegistry);
|
||||
bindContributionProvider(bind, CommandContribution);
|
||||
|
||||
bind(ContextKeyService).to(ContextKeyServiceDummyImpl).inSingletonScope();
|
||||
|
||||
bind(MenuModelRegistry).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, MenuContribution);
|
||||
|
||||
bind(KeyboardLayoutService).toSelf().inSingletonScope();
|
||||
bind(KeybindingRegistry).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, KeybindingContext);
|
||||
bindContributionProvider(bind, KeybindingContribution);
|
||||
|
||||
bindMessageService(bind).onActivation(({ container }, messages) => {
|
||||
const client = container.get(MessageClient);
|
||||
WebSocketConnectionProvider.createHandler(container, messageServicePath, client);
|
||||
return messages;
|
||||
});
|
||||
|
||||
bind(LanguageService).toSelf().inSingletonScope();
|
||||
|
||||
bind(EncodingService).toSelf().inSingletonScope();
|
||||
bind(EncodingRegistry).toSelf().inSingletonScope();
|
||||
|
||||
bind(ResourceContextKey).toSelf().inSingletonScope();
|
||||
bind(CommonFrontendContribution).toSelf().inSingletonScope();
|
||||
[FrontendApplicationContribution, CommandContribution, KeybindingContribution, MenuContribution, ColorContribution].forEach(serviceIdentifier =>
|
||||
bind(serviceIdentifier).toService(CommonFrontendContribution)
|
||||
);
|
||||
bind(SymbolIconColorContribution).toSelf().inSingletonScope();
|
||||
bind(ColorContribution).toService(SymbolIconColorContribution);
|
||||
|
||||
bindCommonStylingParticipants(bind);
|
||||
|
||||
bind(QuickCommandFrontendContribution).toSelf().inSingletonScope();
|
||||
[CommandContribution, KeybindingContribution, MenuContribution].forEach(serviceIdentifier =>
|
||||
bind(serviceIdentifier).toService(QuickCommandFrontendContribution)
|
||||
);
|
||||
bind(QuickCommandService).toSelf().inSingletonScope();
|
||||
bind(QuickAccessContribution).toService(QuickCommandService);
|
||||
|
||||
bind(QuickHelpService).toSelf().inSingletonScope();
|
||||
bind(QuickAccessContribution).toService(QuickHelpService);
|
||||
|
||||
bind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope().onActivation(({ container }, quickPickService: QuickPickService) => {
|
||||
WebSocketConnectionProvider.createHandler(container, quickPickServicePath, quickPickService);
|
||||
return quickPickService;
|
||||
});
|
||||
|
||||
bind(MarkdownRenderer).to(MarkdownRendererImpl).inSingletonScope();
|
||||
bind(MarkdownRendererFactory).toFactory(({ container }) => () => container.get(MarkdownRenderer));
|
||||
|
||||
bindContributionProvider(bind, QuickAccessContribution);
|
||||
bind(QuickInputFrontendContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(QuickInputFrontendContribution);
|
||||
|
||||
bind(LocalStorageService).toSelf().inSingletonScope();
|
||||
bind(StorageService).toService(LocalStorageService);
|
||||
|
||||
bindStatusBar(bind);
|
||||
bind(LabelParser).toSelf().inSingletonScope();
|
||||
|
||||
bindContributionProvider(bind, LabelProviderContribution);
|
||||
bind(LabelProvider).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(LabelProvider);
|
||||
bind(DefaultUriLabelProviderContribution).toSelf().inSingletonScope();
|
||||
bind(LabelProviderContribution).toService(DefaultUriLabelProviderContribution);
|
||||
bind(LabelProviderContribution).to(DiffUriLabelProviderContribution).inSingletonScope();
|
||||
|
||||
bind(TreeLabelProvider).toSelf().inSingletonScope();
|
||||
bind(LabelProviderContribution).toService(TreeLabelProvider);
|
||||
|
||||
bindPreferenceService(bind);
|
||||
bind(FrontendApplicationContribution).toService(PreferenceService);
|
||||
|
||||
bindContributionProvider(bind, JsonSchemaContribution);
|
||||
bind(JsonSchemaStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(JsonSchemaStore);
|
||||
bind(JsonSchemaDataStore).toSelf().inSingletonScope();
|
||||
bind(DefaultJsonSchemaContribution).toSelf().inSingletonScope();
|
||||
bind(JsonSchemaContribution).toService(DefaultJsonSchemaContribution);
|
||||
|
||||
bind(PingService).toDynamicValue(ctx => {
|
||||
// let's reuse a simple and cheap service from this package
|
||||
const envServer: EnvVariablesServer = ctx.container.get(EnvVariablesServer);
|
||||
return {
|
||||
ping(): Promise<EnvVariable | undefined> {
|
||||
return envServer.getValue('does_not_matter');
|
||||
}
|
||||
};
|
||||
});
|
||||
bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
|
||||
bind(ConnectionStatusService).toService(FrontendConnectionStatusService);
|
||||
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(ApplicationConnectionStatusContribution);
|
||||
|
||||
bind(ApplicationServer).toDynamicValue(ctx => {
|
||||
const provider = ctx.container.get(WebSocketConnectionProvider);
|
||||
return provider.createProxy<ApplicationServer>(applicationPath);
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(AboutDialog).toSelf().inSingletonScope();
|
||||
bind(AboutDialogProps).toConstantValue({ title: 'Theia' });
|
||||
|
||||
bind(EnvVariablesServer).toDynamicValue(ctx => {
|
||||
const connection = ctx.container.get(WebSocketConnectionProvider);
|
||||
return connection.createProxy<EnvVariablesServer>(envVariablesPath);
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(ThemeService).toSelf().inSingletonScope();
|
||||
|
||||
bindCorePreferences(bind);
|
||||
bindTreePreferences(bind);
|
||||
|
||||
bind(MimeService).toSelf().inSingletonScope();
|
||||
|
||||
bind(ApplicationShellMouseTracker).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(ApplicationShellMouseTracker);
|
||||
|
||||
bind(ViewContainer.Factory).toFactory(context => (options: ViewContainerIdentifier) => {
|
||||
const container = context.container.createChild();
|
||||
container.bind(ViewContainerIdentifier).toConstantValue(options);
|
||||
container.bind(ViewContainer).toSelf().inSingletonScope();
|
||||
return container.get(ViewContainer);
|
||||
});
|
||||
|
||||
bind(QuickViewService).toSelf().inSingletonScope();
|
||||
bind(QuickAccessContribution).toService(QuickViewService);
|
||||
|
||||
bind(DialogOverlayService).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(DialogOverlayService);
|
||||
|
||||
bind(DispatchingProgressClient).toSelf().inSingletonScope();
|
||||
bind(ProgressLocationService).toSelf().inSingletonScope();
|
||||
bind(ProgressStatusBarItem).toSelf().inSingletonScope();
|
||||
bind(ProgressClient).toService(DispatchingProgressClient);
|
||||
bind(ProgressService).toSelf().inSingletonScope();
|
||||
bind(ProgressBarFactory).toFactory(context => (options: ProgressBarOptions) => {
|
||||
const childContainer = context.container.createChild();
|
||||
childContainer.bind(ProgressBarOptions).toConstantValue(options);
|
||||
childContainer.bind(ProgressBar).toSelf().inSingletonScope();
|
||||
return childContainer.get(ProgressBar);
|
||||
});
|
||||
|
||||
bind(ContextMenuContext).toSelf().inSingletonScope();
|
||||
|
||||
bind(AuthenticationService).to(AuthenticationServiceImpl).inSingletonScope();
|
||||
bind(DecorationsService).to(DecorationsServiceImpl).inSingletonScope();
|
||||
|
||||
bind(KeyStoreService).toDynamicValue(ctx => {
|
||||
const connection = ctx.container.get(WebSocketConnectionProvider);
|
||||
return connection.createProxy<KeyStoreService>(keyStoreServicePath);
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(CredentialsService).to(CredentialsServiceImpl);
|
||||
|
||||
bind(ContributionFilterRegistry).to(ContributionFilterRegistryImpl).inSingletonScope();
|
||||
bind(WindowContribution).toSelf().inSingletonScope();
|
||||
for (const contribution of [CommandContribution, KeybindingContribution, MenuContribution]) {
|
||||
bind(contribution).toService(WindowContribution);
|
||||
}
|
||||
bind(WindowTitleService).toSelf().inSingletonScope();
|
||||
bind(WindowTitleUpdater).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(WindowTitleUpdater);
|
||||
bindContributionProvider(bind, BreadcrumbsContribution);
|
||||
bind(BreadcrumbsService).toSelf().inSingletonScope();
|
||||
bind(BreadcrumbsRenderer).toSelf();
|
||||
bind(BreadcrumbsRendererFactory).toFactory(ctx =>
|
||||
() => {
|
||||
const childContainer = ctx.container.createChild();
|
||||
childContainer.bind(BreadcrumbRenderer).to(DefaultBreadcrumbRenderer).inSingletonScope();
|
||||
return childContainer.get(BreadcrumbsRenderer);
|
||||
}
|
||||
);
|
||||
bind(BreadcrumbPopupContainer).toSelf();
|
||||
bind(BreadcrumbPopupContainerFactory).toFactory(({ container }) => (parent: HTMLElement, breadcrumbId: string, position: Coordinate): BreadcrumbPopupContainer => {
|
||||
const child = container.createChild();
|
||||
child.bind(RendererHost).toConstantValue(parent);
|
||||
child.bind(BreadcrumbID).toConstantValue(breadcrumbId);
|
||||
child.bind(Coordinate).toConstantValue(position);
|
||||
return child.get(BreadcrumbPopupContainer);
|
||||
});
|
||||
|
||||
bind(BackendRequestService).toDynamicValue(ctx =>
|
||||
WebSocketConnectionProvider.createProxy<RequestService>(ctx.container, REQUEST_SERVICE_PATH)
|
||||
).inSingletonScope();
|
||||
|
||||
bindFrontendStopwatch(bind);
|
||||
bindBackendStopwatch(bind);
|
||||
|
||||
bind(SaveableService).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(SaveableService);
|
||||
|
||||
bind(UserWorkingDirectoryProvider).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(UserWorkingDirectoryProvider);
|
||||
|
||||
bind(HoverService).toSelf().inSingletonScope();
|
||||
|
||||
bind(StylingService).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, StylingParticipant);
|
||||
bind(FrontendApplicationContribution).toService(StylingService);
|
||||
|
||||
bind(SecondaryWindowHandler).toSelf().inSingletonScope();
|
||||
bind(ViewColumnService).toSelf().inSingletonScope();
|
||||
|
||||
bind(UndoRedoHandlerService).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, UndoRedoHandler);
|
||||
bind(DomInputUndoRedoHandler).toSelf().inSingletonScope();
|
||||
bind(UndoRedoHandler).toService(DomInputUndoRedoHandler);
|
||||
|
||||
bind(WidgetStatusBarService).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(WidgetStatusBarService);
|
||||
bindContributionProvider(bind, WidgetStatusBarContribution);
|
||||
bindBadgeDecoration(bind);
|
||||
});
|
||||
74
packages/core/src/browser/frontend-application-state.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { Emitter, Event } from '../common/event';
|
||||
import { Deferred } from '../common/promise-util';
|
||||
import { ILogger } from '../common/logger';
|
||||
import { FrontendApplicationState } from '../common/frontend-application-state';
|
||||
|
||||
export { FrontendApplicationState };
|
||||
|
||||
@injectable()
|
||||
export class FrontendApplicationStateService {
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
private _state: FrontendApplicationState = 'init';
|
||||
|
||||
protected deferred: { [state: string]: Deferred<void> } = {};
|
||||
protected readonly stateChanged = new Emitter<FrontendApplicationState>();
|
||||
|
||||
get state(): FrontendApplicationState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set state(state: FrontendApplicationState) {
|
||||
if (state !== this._state) {
|
||||
this.doSetState(state);
|
||||
}
|
||||
}
|
||||
|
||||
get onStateChanged(): Event<FrontendApplicationState> {
|
||||
return this.stateChanged.event;
|
||||
}
|
||||
|
||||
protected doSetState(state: FrontendApplicationState): void {
|
||||
if (this.deferred[this._state] === undefined) {
|
||||
this.deferred[this._state] = new Deferred();
|
||||
}
|
||||
const oldState = this._state;
|
||||
this._state = state;
|
||||
if (this.deferred[state] === undefined) {
|
||||
this.deferred[state] = new Deferred();
|
||||
}
|
||||
this.deferred[state].resolve();
|
||||
this.logger.info(`Changed application state from '${oldState}' to '${this._state}'.`);
|
||||
this.stateChanged.fire(state);
|
||||
}
|
||||
|
||||
reachedState(state: FrontendApplicationState): Promise<void> {
|
||||
if (this.deferred[state] === undefined) {
|
||||
this.deferred[state] = new Deferred();
|
||||
}
|
||||
return this.deferred[state].promise;
|
||||
}
|
||||
|
||||
reachedAnyState(...states: FrontendApplicationState[]): Promise<void> {
|
||||
return Promise.race(states.map(s => this.reachedState(s)));
|
||||
}
|
||||
}
|
||||
326
packages/core/src/browser/frontend-application.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, named } from 'inversify';
|
||||
import { ContributionProvider, CommandRegistry, MenuModelRegistry, isOSX, BackendStopwatch, LogLevel, Stopwatch } from '../common';
|
||||
import { MaybePromise } from '../common/types';
|
||||
import { KeybindingRegistry } from './keybinding';
|
||||
import { Widget } from './widgets';
|
||||
import { ApplicationShell } from './shell/application-shell';
|
||||
import { ShellLayoutRestorer, ApplicationShellLayoutMigrationError } from './shell/shell-layout-restorer';
|
||||
import { FrontendApplicationStateService } from './frontend-application-state';
|
||||
import { preventNavigation, parseCssTime, animationFrame } from './browser';
|
||||
import { CorePreferences } from '../common/core-preferences';
|
||||
import { WindowService } from './window/window-service';
|
||||
import { TooltipService } from './tooltip-service';
|
||||
import { FrontendApplicationContribution } from './frontend-application-contribution';
|
||||
|
||||
const TIMER_WARNING_THRESHOLD = 100;
|
||||
|
||||
@injectable()
|
||||
export class FrontendApplication {
|
||||
|
||||
@inject(CorePreferences)
|
||||
protected readonly corePreferences: CorePreferences;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowsService: WindowService;
|
||||
|
||||
@inject(TooltipService)
|
||||
protected readonly tooltipService: TooltipService;
|
||||
|
||||
@inject(Stopwatch)
|
||||
protected readonly stopwatch: Stopwatch;
|
||||
|
||||
@inject(BackendStopwatch)
|
||||
protected readonly backendStopwatch: BackendStopwatch;
|
||||
|
||||
constructor(
|
||||
@inject(CommandRegistry) protected readonly commands: CommandRegistry,
|
||||
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry,
|
||||
@inject(KeybindingRegistry) protected readonly keybindings: KeybindingRegistry,
|
||||
@inject(ShellLayoutRestorer) protected readonly layoutRestorer: ShellLayoutRestorer,
|
||||
@inject(ContributionProvider) @named(FrontendApplicationContribution)
|
||||
protected readonly contributions: ContributionProvider<FrontendApplicationContribution>,
|
||||
@inject(ApplicationShell) protected readonly _shell: ApplicationShell,
|
||||
@inject(FrontendApplicationStateService) protected readonly stateService: FrontendApplicationStateService
|
||||
) { }
|
||||
|
||||
get shell(): ApplicationShell {
|
||||
return this._shell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the frontend application.
|
||||
*
|
||||
* Start up consists of the following steps:
|
||||
* - start frontend contributions
|
||||
* - attach the application shell to the host element
|
||||
* - initialize the application shell layout
|
||||
* - reveal the application shell if it was hidden by a startup indicator
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
const startup = this.backendStopwatch.start('frontend');
|
||||
|
||||
await this.measure('startContributions', () => this.startContributions(), 'Start frontend contributions', false);
|
||||
this.stateService.state = 'started_contributions';
|
||||
|
||||
const host = await this.getHost();
|
||||
this.attachShell(host);
|
||||
this.attachTooltip(host);
|
||||
await animationFrame();
|
||||
this.stateService.state = 'attached_shell';
|
||||
|
||||
await this.measure('initializeLayout', () => this.initializeLayout(), 'Initialize the workbench layout', false);
|
||||
this.stateService.state = 'initialized_layout';
|
||||
await this.fireOnDidInitializeLayout();
|
||||
|
||||
await this.measure('revealShell', () => this.revealShell(host), 'Replace loading indicator with ready workbench UI (animation)', false);
|
||||
this.registerEventListeners();
|
||||
this.stateService.state = 'ready';
|
||||
|
||||
startup.then(idToken => this.backendStopwatch.stop(idToken, 'Frontend application start', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a promise to the host element to which the application shell is attached.
|
||||
*/
|
||||
protected getHost(): Promise<HTMLElement> {
|
||||
if (document.body) {
|
||||
return Promise.resolve(document.body);
|
||||
}
|
||||
return new Promise<HTMLElement>(resolve =>
|
||||
window.addEventListener('load', () => resolve(document.body), { once: true })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an HTML element that indicates the startup phase, e.g. with an animation or a splash screen.
|
||||
*/
|
||||
protected getStartupIndicator(host: HTMLElement): HTMLElement | undefined {
|
||||
const startupElements = host.getElementsByClassName('theia-preload');
|
||||
return startupElements.length === 0 ? undefined : startupElements[0] as HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register global event listeners.
|
||||
*/
|
||||
protected registerEventListeners(): void {
|
||||
this.windowsService.onUnload(() => {
|
||||
this.stateService.state = 'closing_window';
|
||||
this.layoutRestorer.storeLayout(this);
|
||||
this.stopContributions();
|
||||
});
|
||||
window.addEventListener('resize', () => this.shell.update());
|
||||
|
||||
this.keybindings.registerEventListeners(window);
|
||||
|
||||
document.addEventListener('touchmove', event => { event.preventDefault(); }, { passive: false });
|
||||
// Prevent forward/back navigation by scrolling in OS X
|
||||
if (isOSX) {
|
||||
document.body.addEventListener('wheel', preventNavigation, { passive: false });
|
||||
}
|
||||
// Prevent the default browser behavior when dragging and dropping files into the window.
|
||||
document.addEventListener('dragenter', event => {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
document.addEventListener('dragover', event => {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
} event.preventDefault();
|
||||
}, false);
|
||||
document.addEventListener('drop', event => {
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the application shell to the host element. If a startup indicator is present, the shell is
|
||||
* inserted before that indicator so it is not visible yet.
|
||||
*/
|
||||
protected attachShell(host: HTMLElement): void {
|
||||
const ref = this.getStartupIndicator(host);
|
||||
Widget.attach(this.shell, host, ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the tooltip container to the host element.
|
||||
*/
|
||||
protected attachTooltip(host: HTMLElement): void {
|
||||
this.tooltipService.attachTo(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a startup indicator is present, it is first hidden with the `theia-hidden` CSS class and then
|
||||
* removed after a while. The delay until removal is taken from the CSS transition duration.
|
||||
*/
|
||||
protected revealShell(host: HTMLElement): Promise<void> {
|
||||
const startupElem = this.getStartupIndicator(host);
|
||||
if (startupElem) {
|
||||
return new Promise(resolve => {
|
||||
window.requestAnimationFrame(() => {
|
||||
startupElem.classList.add('theia-hidden');
|
||||
const preloadStyle = window.getComputedStyle(startupElem);
|
||||
const transitionDuration = parseCssTime(preloadStyle.transitionDuration, 0);
|
||||
window.setTimeout(() => {
|
||||
const parent = startupElem.parentElement;
|
||||
if (parent) {
|
||||
parent.removeChild(startupElem);
|
||||
}
|
||||
resolve();
|
||||
}, transitionDuration);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the shell layout either using the layout restorer service or, if no layout has
|
||||
* been stored, by creating the default layout.
|
||||
*/
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
if (!await this.restoreLayout()) {
|
||||
// Fallback: Create the default shell layout
|
||||
await this.createDefaultLayout();
|
||||
}
|
||||
await this.shell.pendingUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to restore the shell layout from the storage service. Resolves to `true` if successful.
|
||||
*/
|
||||
protected async restoreLayout(): Promise<boolean> {
|
||||
try {
|
||||
return await this.layoutRestorer.restoreLayout(this);
|
||||
} catch (error) {
|
||||
if (ApplicationShellLayoutMigrationError.is(error)) {
|
||||
console.warn(error.message);
|
||||
console.info('Initializing the default layout instead...');
|
||||
} else {
|
||||
console.error('Could not restore layout', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the frontend application contributions initialize the shell layout. Override this
|
||||
* method in order to create an application-specific custom layout.
|
||||
*/
|
||||
protected async createDefaultLayout(): Promise<void> {
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.initializeLayout) {
|
||||
await this.measure(contribution.constructor.name + '.initializeLayout',
|
||||
() => contribution.initializeLayout!(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async fireOnDidInitializeLayout(): Promise<void> {
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.onDidInitializeLayout) {
|
||||
await this.measure(contribution.constructor.name + '.onDidInitializeLayout',
|
||||
() => contribution.onDidInitializeLayout!(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and start the frontend application contributions.
|
||||
*/
|
||||
protected async startContributions(): Promise<void> {
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.initialize) {
|
||||
try {
|
||||
await this.measure(contribution.constructor.name + '.initialize',
|
||||
() => contribution.initialize!()
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Could not initialize contribution', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.configure) {
|
||||
try {
|
||||
await this.measure(contribution.constructor.name + '.configure',
|
||||
() => contribution.configure!(this)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Could not configure contribution', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME:
|
||||
* - decouple commands & menus
|
||||
* - consider treat commands, keybindings and menus as frontend application contributions
|
||||
*/
|
||||
await this.measure('commands.onStart',
|
||||
() => this.commands.onStart()
|
||||
);
|
||||
await this.measure('keybindings.onStart',
|
||||
() => this.keybindings.onStart()
|
||||
);
|
||||
await this.measure('menus.onStart',
|
||||
() => this.menus.onStart()
|
||||
);
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.onStart) {
|
||||
try {
|
||||
await this.measure(contribution.constructor.name + '.onStart',
|
||||
() => contribution.onStart!(this)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Could not start contribution', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the frontend application contributions. This is called when the window is unloaded.
|
||||
*/
|
||||
protected stopContributions(): void {
|
||||
console.info('>>> Stopping frontend contributions...');
|
||||
for (const contribution of this.contributions.getContributions()) {
|
||||
if (contribution.onStop) {
|
||||
try {
|
||||
contribution.onStop(this);
|
||||
} catch (error) {
|
||||
console.error('Could not stop contribution', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.info('<<< All frontend contributions have been stopped.');
|
||||
}
|
||||
|
||||
protected async measure<T>(name: string, fn: () => MaybePromise<T>, message = `Frontend ${name}`, threshold = true): Promise<T> {
|
||||
return this.stopwatch.startAsync(name, message, fn,
|
||||
threshold ? { thresholdMillis: TIMER_WARNING_THRESHOLD, defaultLogLevel: LogLevel.DEBUG } : {});
|
||||
}
|
||||
|
||||
}
|
||||
280
packages/core/src/browser/hover-service.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2022 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Disposable, DisposableCollection, disposableTimeout, isOSX, PreferenceService } from '../common';
|
||||
import { MarkdownString } from '../common/markdown-rendering/markdown-string';
|
||||
import { animationFrame } from './browser';
|
||||
import { MarkdownRenderer, MarkdownRendererFactory } from './markdown-rendering/markdown-renderer';
|
||||
|
||||
import '../../src/browser/style/hover-service.css';
|
||||
|
||||
export type HoverPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||
|
||||
// Threshold, in milliseconds, over which a mouse movement is not considered
|
||||
// quick enough as to be ignored
|
||||
const quickMouseThresholdMillis = 200;
|
||||
|
||||
export namespace HoverPosition {
|
||||
export function invertIfNecessary(position: HoverPosition, target: DOMRect, host: DOMRect, totalWidth: number, totalHeight: number): HoverPosition {
|
||||
if (position === 'left') {
|
||||
if (target.left - host.width - 5 < 0) {
|
||||
return 'right';
|
||||
}
|
||||
} else if (position === 'right') {
|
||||
if (target.right + host.width + 5 > totalWidth) {
|
||||
return 'left';
|
||||
}
|
||||
} else if (position === 'top') {
|
||||
if (target.top - host.height - 5 < 0) {
|
||||
return 'bottom';
|
||||
}
|
||||
} else if (position === 'bottom') {
|
||||
if (target.bottom + host.height + 5 > totalHeight) {
|
||||
return 'top';
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
export interface HoverRequest {
|
||||
content: string | MarkdownString | HTMLElement
|
||||
target: HTMLElement
|
||||
/**
|
||||
* The position where the hover should appear.
|
||||
* Note that the hover service will try to invert the position (i.e. right -> left)
|
||||
* if the specified content does not fit in the window next to the target element
|
||||
*/
|
||||
position: HoverPosition
|
||||
/**
|
||||
* Additional css classes that should be added to the hover box.
|
||||
* Used to style certain boxes different e.g. for the extended tab preview.
|
||||
*/
|
||||
cssClasses?: string[]
|
||||
/**
|
||||
* A function to render a visual preview on the hover.
|
||||
* Function that takes the desired width and returns a HTMLElement to be rendered.
|
||||
*/
|
||||
visualPreview?: (width: number) => HTMLElement | undefined;
|
||||
/**
|
||||
* Indicates if the hover contains interactive/clickable items.
|
||||
* When true, the hover will register a click handler to allow interaction with elements in the hover area.
|
||||
*/
|
||||
interactive?: boolean;
|
||||
/**
|
||||
* If implemented, this method will be called when the hover is no longer shown or no longer scheduled to be shown.
|
||||
*/
|
||||
onHide?(): void;
|
||||
/**
|
||||
* When true, the hover will be shown immediately without any delay.
|
||||
* Useful for explicitly triggered hovers (e.g., on click) where the user expects instant feedback.
|
||||
* @default false
|
||||
*/
|
||||
skipHoverDelay?: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class HoverService {
|
||||
protected static hostClassName = 'theia-hover';
|
||||
protected static styleSheetId = 'theia-hover-style';
|
||||
@inject(PreferenceService) protected readonly preferences: PreferenceService;
|
||||
@inject(MarkdownRendererFactory) protected readonly markdownRendererFactory: MarkdownRendererFactory;
|
||||
|
||||
protected _markdownRenderer: MarkdownRenderer | undefined;
|
||||
protected get markdownRenderer(): MarkdownRenderer {
|
||||
this._markdownRenderer ||= this.markdownRendererFactory();
|
||||
return this._markdownRenderer;
|
||||
}
|
||||
|
||||
protected _hoverHost: HTMLElement | undefined;
|
||||
protected get hoverHost(): HTMLElement {
|
||||
if (!this._hoverHost) {
|
||||
this._hoverHost = document.createElement('div');
|
||||
this._hoverHost.classList.add(HoverService.hostClassName);
|
||||
this._hoverHost.style.position = 'absolute';
|
||||
this._hoverHost.setAttribute('popover', 'hint');
|
||||
}
|
||||
return this._hoverHost;
|
||||
}
|
||||
protected pendingTimeout: Disposable | undefined;
|
||||
protected hoverTarget: HTMLElement | undefined;
|
||||
protected lastHidHover = Date.now();
|
||||
protected readonly disposeOnHide = new DisposableCollection();
|
||||
|
||||
requestHover(request: HoverRequest): void {
|
||||
this.cancelHover();
|
||||
const delay = request.skipHoverDelay ? 0 : this.getHoverDelay();
|
||||
this.pendingTimeout = disposableTimeout(() => this.renderHover(request), delay);
|
||||
this.hoverTarget = request.target;
|
||||
this.listenForMouseOut();
|
||||
this.listenForMouseClick(request);
|
||||
}
|
||||
|
||||
protected getHoverDelay(): number {
|
||||
return Date.now() - this.lastHidHover < quickMouseThresholdMillis
|
||||
? 0
|
||||
: this.preferences.get('workbench.hover.delay', isOSX ? 1500 : 500);
|
||||
}
|
||||
|
||||
protected async renderHover(request: HoverRequest): Promise<void> {
|
||||
const host = this.hoverHost;
|
||||
let firstChild: HTMLElement | undefined;
|
||||
const { target, content, position, cssClasses, interactive, onHide } = request;
|
||||
if (onHide) {
|
||||
this.disposeOnHide.push({ dispose: onHide.bind(request) });
|
||||
}
|
||||
if (cssClasses) {
|
||||
host.classList.add(...cssClasses);
|
||||
}
|
||||
if (content instanceof HTMLElement) {
|
||||
host.appendChild(content);
|
||||
firstChild = content;
|
||||
} else if (typeof content === 'string') {
|
||||
host.textContent = content;
|
||||
} else {
|
||||
const renderedContent = this.markdownRenderer.render(content);
|
||||
this.disposeOnHide.push(renderedContent);
|
||||
host.appendChild(renderedContent.element);
|
||||
firstChild = renderedContent.element;
|
||||
}
|
||||
// browsers might insert linebreaks when the hover appears at the edge of the window
|
||||
// resetting the position prevents that
|
||||
host.style.left = '0px';
|
||||
host.style.top = '0px';
|
||||
document.body.append(host);
|
||||
if (!host.matches(':popover-open')) {
|
||||
host.showPopover();
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
// Add a click handler to the hover host to ensure clicks within the hover area work properly
|
||||
const clickHandler = (e: MouseEvent) => {
|
||||
// Let click events within the hover area be processed by their handlers
|
||||
// but prevent them from triggering document handlers that might dismiss the tooltip
|
||||
e.stopImmediatePropagation();
|
||||
};
|
||||
host.addEventListener('click', clickHandler);
|
||||
this.disposeOnHide.push({ dispose: () => host.removeEventListener('click', clickHandler) });
|
||||
}
|
||||
|
||||
if (request.visualPreview) {
|
||||
// If just a string is being rendered use the size of the outer box
|
||||
const width = firstChild ? firstChild.offsetWidth : this.hoverHost.offsetWidth;
|
||||
const visualPreview = request.visualPreview(width);
|
||||
if (visualPreview) {
|
||||
host.appendChild(visualPreview);
|
||||
}
|
||||
}
|
||||
|
||||
await animationFrame(); // Allow the browser to size the host
|
||||
const updatedPosition = this.setHostPosition(target, host, position);
|
||||
|
||||
this.disposeOnHide.push({
|
||||
dispose: () => {
|
||||
this.lastHidHover = Date.now();
|
||||
host.classList.remove(updatedPosition);
|
||||
if (cssClasses) {
|
||||
host.classList.remove(...cssClasses);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected setHostPosition(target: HTMLElement, host: HTMLElement, position: HoverPosition): HoverPosition {
|
||||
const targetDimensions = target.getBoundingClientRect();
|
||||
const hostDimensions = host.getBoundingClientRect();
|
||||
const documentWidth = document.body.getBoundingClientRect().width;
|
||||
// document.body.getBoundingClientRect().height doesn't work as expected
|
||||
// scrollHeight will always be accurate here: https://stackoverflow.com/a/44077777
|
||||
const documentHeight = document.documentElement.scrollHeight;
|
||||
position = HoverPosition.invertIfNecessary(position, targetDimensions, hostDimensions, documentWidth, documentHeight);
|
||||
if (position === 'top' || position === 'bottom') {
|
||||
const targetMiddleWidth = targetDimensions.left + (targetDimensions.width / 2);
|
||||
const middleAlignment = targetMiddleWidth - (hostDimensions.width / 2);
|
||||
const furthestRight = Math.min(documentWidth - hostDimensions.width, middleAlignment);
|
||||
const left = Math.max(0, furthestRight);
|
||||
const top = position === 'top'
|
||||
? targetDimensions.top - hostDimensions.height - 5
|
||||
: targetDimensions.bottom + 5;
|
||||
host.style.setProperty('--theia-hover-before-position', `${targetMiddleWidth - left - 5}px`);
|
||||
host.style.top = `${top}px`;
|
||||
host.style.left = `${left}px`;
|
||||
} else {
|
||||
const targetMiddleHeight = targetDimensions.top + (targetDimensions.height / 2);
|
||||
const middleAlignment = targetMiddleHeight - (hostDimensions.height / 2);
|
||||
const furthestTop = Math.min(documentHeight - hostDimensions.height, middleAlignment);
|
||||
const top = Math.max(0, furthestTop);
|
||||
const left = position === 'left'
|
||||
? targetDimensions.left - hostDimensions.width - 5
|
||||
: targetDimensions.right + 5;
|
||||
host.style.setProperty('--theia-hover-before-position', `${targetMiddleHeight - top - 5}px`);
|
||||
host.style.left = `${left}px`;
|
||||
host.style.top = `${top}px`;
|
||||
}
|
||||
host.classList.add(position);
|
||||
return position;
|
||||
}
|
||||
|
||||
protected listenForMouseOut(): void {
|
||||
const handleMouseLeave = (e: MouseEvent) => {
|
||||
this.disposeOnHide.push(disposableTimeout(() => {
|
||||
if (!this.hoverHost.matches(':hover') && !this.hoverTarget?.matches(':hover')) {
|
||||
this.cancelHover();
|
||||
}
|
||||
}, quickMouseThresholdMillis));
|
||||
};
|
||||
this.hoverTarget?.addEventListener('mouseout', handleMouseLeave);
|
||||
this.hoverHost.addEventListener('mouseout', handleMouseLeave);
|
||||
this.disposeOnHide.push({
|
||||
dispose: () => {
|
||||
this.hoverTarget?.removeEventListener('mouseout', handleMouseLeave);
|
||||
this.hoverHost.removeEventListener('mouseout', handleMouseLeave);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancelHover(): void {
|
||||
this.pendingTimeout?.dispose();
|
||||
this.unRenderHover();
|
||||
this.disposeOnHide.dispose();
|
||||
this.hoverTarget = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for mouse click (mousedown) events and handle them based on hover interactivity.
|
||||
* For non-interactive hovers, any mousedown cancels the hover immediately.
|
||||
* For interactive hovers, the hover remains visible to allow interaction with its elements.
|
||||
*/
|
||||
protected listenForMouseClick(request: HoverRequest): void {
|
||||
const handleMouseDown = (_e: MouseEvent) => {
|
||||
const isInteractive = request.interactive;
|
||||
if (!isInteractive) {
|
||||
this.cancelHover();
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleMouseDown, true);
|
||||
this.disposeOnHide.push({ dispose: () => document.removeEventListener('mousedown', handleMouseDown, true) });
|
||||
}
|
||||
|
||||
protected unRenderHover(): void {
|
||||
if (this.hoverHost.matches(':popover-open')) {
|
||||
this.hoverHost.hidePopover();
|
||||
}
|
||||
this.hoverHost.remove();
|
||||
this.hoverHost.replaceChildren();
|
||||
}
|
||||
}
|
||||
49
packages/core/src/browser/http-open-handler.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import URI from '../common/uri';
|
||||
import { OpenHandler } from './opener-service';
|
||||
import { WindowService } from './window/window-service';
|
||||
import { ExternalUriService } from './external-uri-service';
|
||||
|
||||
export interface HttpOpenHandlerOptions {
|
||||
openExternal?: boolean
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class HttpOpenHandler implements OpenHandler {
|
||||
|
||||
static readonly PRIORITY: number = 500;
|
||||
|
||||
readonly id = 'http';
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
|
||||
@inject(ExternalUriService)
|
||||
protected readonly externalUriService: ExternalUriService;
|
||||
|
||||
canHandle(uri: URI, options?: HttpOpenHandlerOptions): number {
|
||||
return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? HttpOpenHandler.PRIORITY : 0;
|
||||
}
|
||||
|
||||
async open(uri: URI): Promise<undefined> {
|
||||
const resolvedUri = await this.externalUriService.resolve(uri);
|
||||
return this.windowService.openNewWindow(resolvedUri.toString(true), { external: true });
|
||||
}
|
||||
|
||||
}
|
||||
27
packages/core/src/browser/i18n/i18n-frontend-module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { AsyncLocalizationProvider, localizationPath } from '../../common/i18n/localization';
|
||||
import { WebSocketConnectionProvider } from '../messaging/ws-connection-provider';
|
||||
import { LanguageQuickPickService } from './language-quick-pick-service';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(AsyncLocalizationProvider).toDynamicValue(
|
||||
ctx => ctx.container.get(WebSocketConnectionProvider).createProxy(localizationPath)
|
||||
).inSingletonScope();
|
||||
bind(LanguageQuickPickService).toSelf().inSingletonScope();
|
||||
});
|
||||
130
packages/core/src/browser/i18n/language-quick-pick-service.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2022 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { nls } from '../../common/nls';
|
||||
import { AsyncLocalizationProvider, LanguageInfo } from '../../common/i18n/localization';
|
||||
import { QuickInputService, QuickPickItem, QuickPickSeparator } from '../quick-input';
|
||||
import { WindowService } from '../window/window-service';
|
||||
|
||||
export interface LanguageQuickPickItem extends QuickPickItem, LanguageInfo {
|
||||
execute?(): Promise<void>
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class LanguageQuickPickService {
|
||||
|
||||
@inject(QuickInputService) protected readonly quickInputService: QuickInputService;
|
||||
@inject(AsyncLocalizationProvider) protected readonly localizationProvider: AsyncLocalizationProvider;
|
||||
@inject(WindowService) protected readonly windowService: WindowService;
|
||||
|
||||
async pickDisplayLanguage(): Promise<LanguageInfo | undefined> {
|
||||
const quickInput = this.quickInputService.createQuickPick<LanguageQuickPickItem>();
|
||||
const installedItems = await this.getInstalledLanguages();
|
||||
const quickInputItems: (LanguageQuickPickItem | QuickPickSeparator)[] = [
|
||||
{
|
||||
type: 'separator',
|
||||
label: nls.localizeByDefault('Installed')
|
||||
},
|
||||
...installedItems
|
||||
];
|
||||
quickInput.items = quickInputItems;
|
||||
quickInput.busy = true;
|
||||
const selected = installedItems.find(item => nls.isSelectedLocale(item.languageId));
|
||||
if (selected) {
|
||||
quickInput.activeItems = [selected];
|
||||
}
|
||||
quickInput.placeholder = nls.localizeByDefault('Configure Display Language');
|
||||
quickInput.show();
|
||||
|
||||
this.getAvailableLanguages().then(availableItems => {
|
||||
if (availableItems.length > 0) {
|
||||
quickInputItems.push({
|
||||
type: 'separator',
|
||||
label: nls.localizeByDefault('Available')
|
||||
});
|
||||
const installed = new Set(installedItems.map(e => e.languageId));
|
||||
for (const available of availableItems) {
|
||||
// Exclude already installed languages
|
||||
if (!installed.has(available.languageId)) {
|
||||
quickInputItems.push(available);
|
||||
}
|
||||
}
|
||||
quickInput.items = quickInputItems;
|
||||
}
|
||||
}).finally(() => {
|
||||
quickInput.busy = false;
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
quickInput.onDidAccept(async () => {
|
||||
const selectedItem = quickInput.selectedItems[0];
|
||||
if (selectedItem) {
|
||||
// Some language quick pick items want to install additional languages
|
||||
// We have to await that before returning the selected locale
|
||||
await selectedItem.execute?.();
|
||||
resolve(selectedItem);
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
quickInput.hide();
|
||||
});
|
||||
quickInput.onDidHide(() => {
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async getInstalledLanguages(): Promise<LanguageQuickPickItem[]> {
|
||||
const languageInfos = await this.localizationProvider.getAvailableLanguages();
|
||||
const items: LanguageQuickPickItem[] = [];
|
||||
const en: LanguageInfo = {
|
||||
languageId: 'en',
|
||||
languageName: 'English',
|
||||
localizedLanguageName: 'English'
|
||||
};
|
||||
languageInfos.push(en);
|
||||
for (const language of languageInfos.filter(e => !!e.languageId)) {
|
||||
items.push(this.createLanguageQuickPickItem(language));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
protected async getAvailableLanguages(): Promise<LanguageQuickPickItem[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected createLanguageQuickPickItem(language: LanguageInfo): LanguageQuickPickItem {
|
||||
let label: string;
|
||||
let description: string | undefined;
|
||||
const languageName = language.localizedLanguageName || language.languageName;
|
||||
const id = language.languageId;
|
||||
const idLabel = id + (nls.isSelectedLocale(id) ? ` (${nls.localizeByDefault('Current')})` : '');
|
||||
if (languageName) {
|
||||
label = languageName;
|
||||
description = idLabel;
|
||||
} else {
|
||||
label = idLabel;
|
||||
}
|
||||
return {
|
||||
label,
|
||||
description,
|
||||
languageId: id,
|
||||
languageName: language.languageName,
|
||||
localizedLanguageName: language.localizedLanguageName
|
||||
};
|
||||
}
|
||||
}
|
||||
87
packages/core/src/browser/icon-registry.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// code copied and modified from https://github.com/Microsoft/vscode/blob/main/src/vs/platform/theme/common/iconRegistry.ts
|
||||
|
||||
import { ThemeIcon } from '../common/theme';
|
||||
import { URI } from 'vscode-uri';
|
||||
|
||||
export interface IconDefinition {
|
||||
font?: IconFontContribution;
|
||||
fontCharacter: string;
|
||||
}
|
||||
|
||||
export interface IconContribution {
|
||||
readonly id: string;
|
||||
description: string | undefined;
|
||||
deprecationMessage?: string;
|
||||
readonly defaults: ThemeIcon | IconDefinition;
|
||||
}
|
||||
|
||||
export interface IconFontContribution {
|
||||
readonly id: string;
|
||||
readonly definition: IconFontDefinition;
|
||||
}
|
||||
|
||||
export interface IconFontDefinition {
|
||||
readonly weight?: string;
|
||||
readonly style?: string;
|
||||
readonly src: IconFontSource[];
|
||||
}
|
||||
|
||||
export interface IconFontSource {
|
||||
readonly location: URI;
|
||||
readonly format: string;
|
||||
}
|
||||
export const IconRegistry = Symbol('IconRegistry');
|
||||
export interface IconRegistry {
|
||||
/**
|
||||
* Register a icon to the registry.
|
||||
* @param id The icon id
|
||||
* @param defaults The default values
|
||||
* @param description The description
|
||||
*/
|
||||
registerIcon(id: string, defaults: ThemeIcon | IconDefinition, description?: string): ThemeIcon;
|
||||
|
||||
/**
|
||||
* Deregister a icon from the registry.
|
||||
* @param id The icon id
|
||||
*/
|
||||
deregisterIcon(id: string): void;
|
||||
|
||||
/**
|
||||
* Register a icon font to the registry.
|
||||
* @param id The icon font id
|
||||
* @param definition The icon font definition
|
||||
*/
|
||||
registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition;
|
||||
|
||||
/**
|
||||
* Deregister an icon font from the registry.
|
||||
* @param id The icon font id
|
||||
*/
|
||||
deregisterIconFont(id: string): void;
|
||||
|
||||
/**
|
||||
* Get the icon font for the given id
|
||||
* @param id The icon font id
|
||||
*/
|
||||
getIconFont(id: string): IconFontDefinition | undefined;
|
||||
}
|
||||
|
||||
64
packages/core/src/browser/icon-theme-contribution.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, named } from 'inversify';
|
||||
import { FrontendApplicationContribution } from './frontend-application-contribution';
|
||||
import { ContributionProvider } from '../common/contribution-provider';
|
||||
import { IconThemeService, IconTheme } from './icon-theme-service';
|
||||
import { MaybePromise } from '../common/types';
|
||||
import { Disposable } from '../common/disposable';
|
||||
|
||||
export const IconThemeContribution = Symbol('IconThemeContribution');
|
||||
export interface IconThemeContribution {
|
||||
registerIconThemes(iconThemes: IconThemeService): MaybePromise<void>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class IconThemeApplicationContribution implements FrontendApplicationContribution {
|
||||
|
||||
@inject(IconThemeService)
|
||||
protected readonly iconThemes: IconThemeService;
|
||||
|
||||
@inject(ContributionProvider) @named(IconThemeContribution)
|
||||
protected readonly iconThemeContributions: ContributionProvider<IconThemeContribution>;
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
for (const contribution of this.iconThemeContributions.getContributions()) {
|
||||
await contribution.registerIconThemes(this.iconThemes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DefaultFileIconThemeContribution implements IconTheme, IconThemeContribution {
|
||||
|
||||
readonly id = 'theia-file-icons';
|
||||
readonly label = 'File Icons (Theia)';
|
||||
readonly hasFileIcons = true;
|
||||
readonly hasFolderIcons = true;
|
||||
readonly showLanguageModeIcons = true;
|
||||
|
||||
registerIconThemes(iconThemes: IconThemeService): MaybePromise<void> {
|
||||
iconThemes.register(this);
|
||||
}
|
||||
|
||||
/* rely on behaviour before for backward-compatibility */
|
||||
activate(): Disposable {
|
||||
return Disposable.NULL;
|
||||
}
|
||||
|
||||
}
|
||||
218
packages/core/src/browser/icon-theme-service.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Emitter } from '../common/event';
|
||||
import { Disposable, DisposableCollection } from '../common/disposable';
|
||||
import { LabelProviderContribution, DidChangeLabelEvent } from './label-provider';
|
||||
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
|
||||
import debounce = require('lodash.debounce');
|
||||
import { PreferenceSchemaService } from '../common/preferences/preference-schema';
|
||||
import { PreferenceService } from '../common/preferences';
|
||||
|
||||
const ICON_THEME_PREFERENCE_KEY = 'workbench.iconTheme';
|
||||
|
||||
export interface IconThemeDefinition {
|
||||
readonly id: string
|
||||
readonly label: string
|
||||
readonly description?: string
|
||||
readonly hasFileIcons?: boolean;
|
||||
readonly hasFolderIcons?: boolean;
|
||||
readonly hidesExplorerArrows?: boolean;
|
||||
readonly showLanguageModeIcons?: boolean;
|
||||
}
|
||||
|
||||
export interface IconTheme extends IconThemeDefinition {
|
||||
activate(): Disposable;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class NoneIconTheme implements IconTheme, LabelProviderContribution {
|
||||
|
||||
readonly id = 'none';
|
||||
readonly label = 'None';
|
||||
readonly description = 'Disable file icons';
|
||||
readonly hasFileIcons = true;
|
||||
readonly hasFolderIcons = true;
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<DidChangeLabelEvent>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
protected readonly toDeactivate = new DisposableCollection();
|
||||
|
||||
activate(): Disposable {
|
||||
if (this.toDeactivate.disposed) {
|
||||
this.toDeactivate.push(Disposable.create(() => this.fireDidChange()));
|
||||
this.fireDidChange();
|
||||
}
|
||||
return this.toDeactivate;
|
||||
}
|
||||
|
||||
protected fireDidChange(): void {
|
||||
this.onDidChangeEmitter.fire({ affects: () => true });
|
||||
}
|
||||
|
||||
canHandle(): number {
|
||||
if (this.toDeactivate.disposed) {
|
||||
return 0;
|
||||
}
|
||||
return Number.MAX_SAFE_INTEGER - 1024;
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class IconThemeService {
|
||||
static readonly STORAGE_KEY = 'iconTheme';
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<void>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
protected readonly _iconThemes = new Map<string, IconTheme>();
|
||||
get ids(): IterableIterator<string> {
|
||||
return this._iconThemes.keys();
|
||||
}
|
||||
get definitions(): IterableIterator<IconThemeDefinition> {
|
||||
return this._iconThemes.values();
|
||||
}
|
||||
getDefinition(id: string): IconThemeDefinition | undefined {
|
||||
return this._iconThemes.get(id);
|
||||
}
|
||||
|
||||
@inject(NoneIconTheme) protected readonly noneIconTheme: NoneIconTheme;
|
||||
@inject(PreferenceService) protected readonly preferences: PreferenceService;
|
||||
@inject(PreferenceSchemaService) protected readonly schemaProvider: PreferenceSchemaService;
|
||||
|
||||
protected readonly onDidChangeCurrentEmitter = new Emitter<string>();
|
||||
readonly onDidChangeCurrent = this.onDidChangeCurrentEmitter.event;
|
||||
|
||||
protected readonly toDeactivate = new DisposableCollection();
|
||||
|
||||
protected activeTheme: IconTheme;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.register(this.fallback);
|
||||
this.setCurrent(this.fallback, false);
|
||||
this.preferences.ready.then(() => {
|
||||
this.validateActiveTheme();
|
||||
this.updateIconThemePreference();
|
||||
this.preferences.onPreferencesChanged(changes => {
|
||||
if (ICON_THEME_PREFERENCE_KEY in changes) {
|
||||
this.validateActiveTheme();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
register(iconTheme: IconTheme): Disposable {
|
||||
if (this._iconThemes.has(iconTheme.id)) {
|
||||
console.warn(new Error(`Icon theme '${iconTheme.id}' has already been registered, skipping.`));
|
||||
return Disposable.NULL;
|
||||
}
|
||||
this._iconThemes.set(iconTheme.id, iconTheme);
|
||||
this.onDidChangeEmitter.fire(undefined);
|
||||
this.validateActiveTheme();
|
||||
this.updateIconThemePreference();
|
||||
return Disposable.create(() => {
|
||||
this.unregister(iconTheme.id);
|
||||
this.updateIconThemePreference();
|
||||
});
|
||||
}
|
||||
|
||||
unregister(id: string): IconTheme | undefined {
|
||||
const iconTheme = this._iconThemes.get(id);
|
||||
if (!iconTheme) {
|
||||
return undefined;
|
||||
}
|
||||
this._iconThemes.delete(id);
|
||||
this.onDidChangeEmitter.fire(undefined);
|
||||
if (id === this.getCurrent().id) {
|
||||
this.setCurrent(this.default, false);
|
||||
}
|
||||
return iconTheme;
|
||||
}
|
||||
|
||||
get current(): string {
|
||||
return this.getCurrent().id;
|
||||
}
|
||||
|
||||
set current(id: string) {
|
||||
const newCurrent = this._iconThemes.get(id);
|
||||
if (newCurrent && this.getCurrent().id !== newCurrent.id) {
|
||||
this.setCurrent(newCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
getCurrent(): IconTheme {
|
||||
return this.activeTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param persistSetting If `true`, the theme's id will be set as the value of the `workbench.iconTheme` preference. (default: `true`)
|
||||
*/
|
||||
setCurrent(newCurrent: IconTheme, persistSetting = true): void {
|
||||
if (newCurrent !== this.getCurrent()) {
|
||||
this.activeTheme = newCurrent;
|
||||
this.toDeactivate.dispose();
|
||||
this.toDeactivate.push(newCurrent.activate());
|
||||
this.onDidChangeCurrentEmitter.fire(newCurrent.id);
|
||||
}
|
||||
if (persistSetting) {
|
||||
this.preferences.updateValue(ICON_THEME_PREFERENCE_KEY, newCurrent.id);
|
||||
}
|
||||
}
|
||||
|
||||
protected getConfiguredTheme(): IconTheme | undefined {
|
||||
const configuredId = this.preferences.get<string>(ICON_THEME_PREFERENCE_KEY);
|
||||
return configuredId ? this._iconThemes.get(configuredId) : undefined;
|
||||
}
|
||||
|
||||
protected validateActiveTheme(): void {
|
||||
if (this.preferences.isReady) {
|
||||
const configured = this.getConfiguredTheme();
|
||||
if (configured && configured !== this.getCurrent()) {
|
||||
this.setCurrent(configured, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateIconThemePreference = debounce(() => this.doUpdateIconThemePreference(), 500);
|
||||
|
||||
protected doUpdateIconThemePreference(): void {
|
||||
const preference = this.schemaProvider.getSchemaProperty(ICON_THEME_PREFERENCE_KEY);
|
||||
if (preference) {
|
||||
const sortedThemes = Array.from(this.definitions).sort((a, b) => a.label.localeCompare(b.label));
|
||||
this.schemaProvider.updateSchemaProperty(ICON_THEME_PREFERENCE_KEY, {
|
||||
...preference,
|
||||
enum: sortedThemes.map(e => e.id),
|
||||
enumItemLabels: sortedThemes.map(e => e.label)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get default(): IconTheme {
|
||||
return this._iconThemes.get(FrontendApplicationConfigProvider.get().defaultIconTheme) || this.fallback;
|
||||
}
|
||||
|
||||
get fallback(): IconTheme {
|
||||
return this.noneIconTheme;
|
||||
}
|
||||
}
|
||||
7
packages/core/src/browser/icons/CollapseAll.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" enable-background="new -1 0 16 16" viewBox="-1 0 16 16">
|
||||
<path fill="#424242" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/>
|
||||
<rect width="3" height="1" x="4" y="9" fill="#00539C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 504 B |
7
packages/core/src/browser/icons/CollapseAll_inverse.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" enable-background="new -1 0 16 16" viewBox="-1 0 16 16">
|
||||
<path fill="#C5C5C5" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/>
|
||||
<rect width="3" height="1" x="4" y="9" fill="#75BEFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 504 B |
7
packages/core/src/browser/icons/Refresh.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path fill="#F6F6F6" d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z"/>
|
||||
<path fill="#424242" d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
7
packages/core/src/browser/icons/Refresh_inverse.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path fill="#2D2D30" d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z"/>
|
||||
<path fill="#C5C5C5" d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
packages/core/src/browser/icons/add-inverse.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 698 B |
4
packages/core/src/browser/icons/add.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 698 B |
6
packages/core/src/browser/icons/arrow-down-bright.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 RedHat and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14642 9.76777L8.14641 14.7678L8.85352 14.7678L13.8535 9.76777L13.1464 9.06066L8.99997 13.2071L8.99997 1L7.99997 0.999999L7.99997 13.2071L3.85353 9.06066L3.14642 9.76777Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 553 B |
6
packages/core/src/browser/icons/arrow-down-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 RedHat and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14646 9.76777L8.14644 14.7678L8.85355 14.7678L13.8535 9.76777L13.1464 9.06066L9 13.2071L9 1L8 0.999999L8 13.2071L3.85356 9.06066L3.14646 9.76777Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 529 B |
6
packages/core/src/browser/icons/arrow-up-bright.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 RedHat and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8536 6.2929L8.85359 1.29291H8.14648L3.14648 6.2929L3.85359 7.00001L8.00003 2.85357V15.0607H9.00003V2.85357L13.1465 7.00001L13.8536 6.2929Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 523 B |
6
packages/core/src/browser/icons/arrow-up-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 RedHat and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8535 6.2929L8.85356 1.29291H8.14645L3.14645 6.2929L3.85356 7.00001L8 2.85357V15.0607H9V2.85357L13.1464 7.00001L13.8535 6.2929Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 511 B |
16
packages/core/src/browser/icons/case-sensitive-dark.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0;fill:#262626;} .st1{fill:#262626;} .st2{fill:#C5C5C5;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
<rect width="16" height="16" class="st0"/>
|
||||
<path d="M14.176 5.592c-.555-.6-1.336-.904-2.322-.904-.258 0-.521.024-.784.072-.246.044-.479.101-.7.169-.228.07-.432.147-.613.229-.22.099-.389.196-.512.284l-.419.299v2.701c-.086.108-.162.223-.229.344l-2.45-6.354h-2.394l-3.753 9.804v.598h3.025l.838-2.35h2.167l.891 2.35h3.237l-.001-.003c.305.092.633.15.993.15.344 0 .671-.049.978-.146h2.853v-4.903c-.001-.975-.271-1.763-.805-2.34z" class="st1"/>
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path d="M7.611 11.834l-.891-2.35h-3.562l-.838 2.35h-1.095l3.217-8.402h1.02l3.24 8.402h-1.091zm-2.531-6.814l-.044-.135-.038-.156-.029-.152-.024-.126h-.023l-.021.126-.032.152-.038.156-.044.135-1.307 3.574h2.918l-1.318-3.574z" class="st2"/>
|
||||
<path d="M13.02 11.834v-.938h-.023c-.199.352-.456.62-.771.806s-.673.278-1.075.278c-.313 0-.588-.045-.826-.135s-.438-.212-.598-.366-.281-.338-.363-.551-.124-.442-.124-.688c0-.262.039-.502.117-.721s.198-.412.36-.58.367-.308.615-.419.544-.19.888-.237l1.811-.252c0-.273-.029-.507-.088-.7s-.143-.351-.252-.472-.241-.21-.396-.267-.325-.085-.513-.085c-.363 0-.714.064-1.052.193s-.638.31-.904.54v-.984c.082-.059.196-.121.343-.188s.312-.128.495-.185.378-.104.583-.141.407-.056.606-.056c.699 0 1.229.194 1.588.583s.539.942.539 1.661v3.902h-.96zm-1.454-2.83c-.273.035-.498.085-.674.149s-.313.144-.41.237-.165.205-.202.334-.055.276-.055.44c0 .141.025.271.076.393s.124.227.22.316.215.16.357.211.308.076.495.076c.242 0 .465-.045.668-.135s.378-.214.524-.372.261-.344.343-.557.123-.442.123-.688v-.609l-1.465.205z" class="st2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
16
packages/core/src/browser/icons/case-sensitive.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0;fill:#F6F6F6;} .st1{fill:#F6F6F6;} .st2{fill:#424242;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
<rect width="16" height="16" class="st0"/>
|
||||
<path d="M14.176 5.592c-.555-.6-1.336-.904-2.322-.904-.258 0-.521.024-.784.072-.246.044-.479.101-.7.169-.228.07-.432.147-.613.229-.22.099-.389.196-.512.284l-.419.299v2.701c-.086.108-.162.223-.229.344l-2.45-6.354h-2.394l-3.753 9.804v.598h3.025l.838-2.35h2.167l.891 2.35h3.237l-.001-.003c.305.092.633.15.993.15.344 0 .671-.049.978-.146h2.853v-4.903c-.001-.975-.271-1.763-.805-2.34z" class="st1"/>
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path d="M7.611 11.834l-.891-2.35h-3.562l-.838 2.35h-1.095l3.217-8.402h1.02l3.24 8.402h-1.091zm-2.531-6.814l-.044-.135-.038-.156-.029-.152-.024-.126h-.023l-.021.126-.032.152-.038.156-.044.135-1.307 3.574h2.918l-1.318-3.574z" class="st2"/>
|
||||
<path d="M13.02 11.834v-.938h-.023c-.199.352-.456.62-.771.806s-.673.278-1.075.278c-.313 0-.588-.045-.826-.135s-.438-.212-.598-.366-.281-.338-.363-.551-.124-.442-.124-.688c0-.262.039-.502.117-.721s.198-.412.36-.58.367-.308.615-.419.544-.19.888-.237l1.811-.252c0-.273-.029-.507-.088-.7s-.143-.351-.252-.472-.241-.21-.396-.267-.325-.085-.513-.085c-.363 0-.714.064-1.052.193s-.638.31-.904.54v-.984c.082-.059.196-.121.343-.188s.312-.128.495-.185.378-.104.583-.141.407-.056.606-.056c.699 0 1.229.194 1.588.583s.539.942.539 1.661v3.902h-.96zm-1.454-2.83c-.273.035-.498.085-.674.149s-.313.144-.41.237-.165.205-.202.334-.055.276-.055.44c0 .141.025.271.076.393s.124.227.22.316.215.16.357.211.308.076.495.076c.242 0 .465-.045.668-.135s.378-.214.524-.372.261-.344.343-.557.123-.442.123-.688v-.609l-1.465.205z" class="st2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
6
packages/core/src/browser/icons/chevron-right-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 TypeFox and others.-->
|
||||
<!--Copied from https://raw.githubusercontent.com/microsoft/vscode-icons/9c90ce81b1f3c309000b80da0763aa09975a85f0/icons/dark/chevron-right.svg -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.7 13.7L5 13L9.6 8.4L5 3.7L5.7 3L10.7 8V8.7L5.7 13.7Z" fill="#C5C5C5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
6
packages/core/src/browser/icons/chevron-right-light.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 TypeFox and others.-->
|
||||
<!--Copied from https://raw.githubusercontent.com/microsoft/vscode-icons/9c90ce81b1f3c309000b80da0763aa09975a85f0/icons/light/chevron-right.svg -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.7 13.7L5 13L9.6 8.4L5 3.7L5.7 3L10.7 8V8.7L5.7 13.7Z" fill="#424242" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
7
packages/core/src/browser/icons/circle-bright.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="#616161" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 18 18">
|
||||
<path fill="none" d="M0,0h18v18H0V0z" />
|
||||
<circle cx="9" cy="9" r="8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
7
packages/core/src/browser/icons/circle-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="#E0E0E0" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 18 18">
|
||||
<path fill="none" d="M0,0h18v18H0V0z" />
|
||||
<circle cx="9" cy="9" r="8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path fill="#E8E8E8" d="M10.798 7l-1.83-2h6.032v2h-4.202zm-2.292-6h-3.207l1.337 1.52 1.87-1.52zm-5.506 8.531v1.469h12v-2h-10.813l-.024.021c-.3.299-.716.479-1.163.51zm0 5.469h12v-2h-12v2zm3.323-8h.631l-.347-.266-.284.266zm8.677-4v-2h-3.289l-1.743 2h5.032z"/>
|
||||
<path fill="#F48771" d="M7.246 4.6l2.856-3.277-.405-.002-3.176 2.581-2.607-2.962c-.336-.221-.786-.2-1.082.096-.308.306-.319.779-.069 1.12l2.83 2.444-3.339 2.466c-.339.338-.339.887 0 1.225.339.337.888.337 1.226 0l3.063-2.867 3.33 2.555h.466l-3.093-3.379z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 805 B |
7
packages/core/src/browser/icons/clear-search-results.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path fill="#424242" d="M10.798 7l-1.83-2h6.032v2h-4.202zm-2.292-6h-3.207l1.337 1.52 1.87-1.52zm-5.506 8.531v1.469h12v-2h-10.813l-.024.021c-.3.299-.716.479-1.163.51zm0 5.469h12v-2h-12v2zm3.323-8h.631l-.347-.266-.284.266zm8.677-4v-2h-3.289l-1.743 2h5.032z"/>
|
||||
<path fill="#A1260D" d="M7.246 4.6l2.856-3.277-.405-.002-3.176 2.581-2.607-2.962c-.336-.221-.786-.2-1.082.096-.308.306-.319.779-.069 1.12l2.83 2.444-3.339 2.466c-.339.338-.339.887 0 1.225.339.337.888.337 1.226 0l3.063-2.867 3.33 2.555h.466l-3.093-3.379z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 805 B |
7
packages/core/src/browser/icons/close-all-bright.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="#000" stroke-width="1.2" d="M11,11 L11,6 L3,6 L3,14 L11,14 L11,11 L14,11 L14,3 L6,3 L6,6"/>
|
||||
<path fill="none" stroke="#000" stroke-width="1.4" d="M5,8 L9,12 M9,8 L5,12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 507 B |
7
packages/core/src/browser/icons/close-all-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="#D4D4D4" stroke-width="1.2" d="M11,11 L11,6 L3,6 L3,14 L11,14 L11,11 L14,11 L14,3 L6,3 L6,6"/>
|
||||
<path fill="none" stroke="#D4D4D4" stroke-width="1.4" d="M5,8 L9,12 M9,8 L5,12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 513 B |
7
packages/core/src/browser/icons/close-bright.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="#000000" height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
7
packages/core/src/browser/icons/close-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="#D4D4D4" height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
4
packages/core/src/browser/icons/collapse.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
6
packages/core/src/browser/icons/edit-json-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 Ericsson and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.22785 7.35986C2.69078 7.35986 3.01627 7.25136 3.20434 7.03436C3.33454 6.87523 3.39964 6.64376 3.39964 6.33996C3.39964 6.20976 3.38517 6.01808 3.35624 5.76492C3.32731 5.51176 3.31284 5.31646 3.31284 5.17902C3.31284 5.04159 3.30561 4.83544 3.29114 4.56058C3.26221 4.31465 3.24774 4.14105 3.24774 4.03978C3.24774 3.34539 3.45027 2.83183 3.85533 2.49909C4.2604 2.16636 4.83183 2 5.56962 2H6.11212V3.28029H5.83002C5.51175 3.28029 5.28391 3.37071 5.14647 3.55154C5.00904 3.73237 4.94033 4.00362 4.94033 4.36528C4.94033 4.45208 4.95479 4.58228 4.98372 4.75588C5.01266 4.97288 5.02712 5.13924 5.02712 5.25497C5.02712 5.34177 5.03436 5.4792 5.04882 5.66727C5.07776 5.92767 5.09222 6.1302 5.09222 6.27486C5.09222 6.82459 4.97649 7.23689 4.74503 7.51175C4.5425 7.75768 4.22423 7.93128 3.79024 8.03255C4.22423 8.14828 4.5425 8.32188 4.74503 8.55334C4.97649 8.84268 5.09222 9.25497 5.09222 9.79024C5.09222 9.94937 5.07776 10.1591 5.04882 10.4195C5.01989 10.6076 5.00904 10.745 5.01627 10.8318C5.02351 10.9186 5.01266 11.0705 4.98372 11.2875C4.95479 11.4467 4.94033 11.5624 4.94033 11.6347C4.94033 11.9964 5.00904 12.2676 5.14647 12.4485C5.28391 12.6293 5.51175 12.7197 5.83002 12.7197H6.11212V14H5.56962C4.0651 14 3.31284 13.3201 3.31284 11.9602C3.31284 11.4684 3.33816 11.0886 3.38879 10.821C3.43942 10.5533 3.46474 10.1664 3.46474 9.66004C3.46474 8.98011 3.08137 8.64014 2.31465 8.64014L2.22785 7.35986ZM13.7722 8.50995C13.0054 8.50995 12.6221 8.84991 12.6221 9.52984C12.6221 9.66004 12.6329 9.85172 12.6546 10.1049C12.6763 10.358 12.6872 10.5497 12.6872 10.6799C12.7306 10.9548 12.7523 11.3382 12.7523 11.83C12.7523 13.1899 11.9855 13.8698 10.4521 13.8698H9.90958V12.7197H10.17C10.4882 12.7197 10.7161 12.6293 10.8535 12.4485C10.991 12.2676 11.0597 11.9964 11.0597 11.6347C11.0597 11.2731 11.038 10.9982 10.9946 10.8101C10.9946 10.6944 10.9801 10.5244 10.9512 10.3002C10.9222 10.0759 10.9078 9.90597 10.9078 9.79024C10.9078 9.25497 11.0235 8.84268 11.255 8.55334C11.4575 8.32188 11.7758 8.14828 12.2098 8.03255C11.7758 7.93128 11.4575 7.75768 11.255 7.51175C11.0235 7.23689 10.9078 6.82459 10.9078 6.27486C10.9078 6.1302 10.9222 5.92767 10.9512 5.66727C10.9801 5.4792 10.9946 5.34177 10.9946 5.25497C11.038 5.02351 11.0597 4.73418 11.0597 4.38698C11.0597 4.03978 10.9873 3.77939 10.8427 3.60579C10.698 3.43219 10.4738 3.32369 10.17 3.28029H9.90958V2H10.4521C11.1754 2 11.7396 2.16636 12.1447 2.49909C12.5497 2.83183 12.7523 3.34539 12.7523 4.03978C12.7523 4.16998 12.7414 4.36166 12.7197 4.61483C12.698 4.86799 12.6872 5.05967 12.6872 5.18987C12.6438 5.46474 12.6148 5.8481 12.6004 6.33996C12.6148 6.64376 12.6799 6.87523 12.7957 7.03436C12.9837 7.25136 13.3092 7.35986 13.7722 7.35986V8.50995Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
6
packages/core/src/browser/icons/edit-json.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 Ericsson and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.22785 7.35986C2.69078 7.35986 3.01627 7.25136 3.20434 7.03436C3.33454 6.87523 3.39964 6.64376 3.39964 6.33996C3.39964 6.20976 3.38517 6.01808 3.35624 5.76492C3.32731 5.51176 3.31284 5.31646 3.31284 5.17902C3.31284 5.04159 3.30561 4.83544 3.29114 4.56058C3.26221 4.31465 3.24774 4.14105 3.24774 4.03978C3.24774 3.34539 3.45027 2.83183 3.85533 2.49909C4.2604 2.16636 4.83183 2 5.56962 2H6.11212V3.28029H5.83002C5.51175 3.28029 5.28391 3.37071 5.14647 3.55154C5.00904 3.73237 4.94033 4.00362 4.94033 4.36528C4.94033 4.45208 4.95479 4.58228 4.98372 4.75588C5.01266 4.97288 5.02712 5.13924 5.02712 5.25497C5.02712 5.34177 5.03436 5.4792 5.04882 5.66727C5.07776 5.92767 5.09222 6.1302 5.09222 6.27486C5.09222 6.82459 4.97649 7.23689 4.74503 7.51175C4.5425 7.75768 4.22423 7.93128 3.79024 8.03255C4.22423 8.14828 4.5425 8.32188 4.74503 8.55334C4.97649 8.84268 5.09222 9.25497 5.09222 9.79024C5.09222 9.94937 5.07776 10.1591 5.04882 10.4195C5.01989 10.6076 5.00904 10.745 5.01627 10.8318C5.02351 10.9186 5.01266 11.0705 4.98372 11.2875C4.95479 11.4467 4.94033 11.5624 4.94033 11.6347C4.94033 11.9964 5.00904 12.2676 5.14647 12.4485C5.28391 12.6293 5.51175 12.7197 5.83002 12.7197H6.11212V14H5.56962C4.0651 14 3.31284 13.3201 3.31284 11.9602C3.31284 11.4684 3.33816 11.0886 3.38879 10.821C3.43942 10.5533 3.46474 10.1664 3.46474 9.66004C3.46474 8.98011 3.08137 8.64014 2.31465 8.64014L2.22785 7.35986ZM13.7722 8.50995C13.0054 8.50995 12.6221 8.84991 12.6221 9.52984C12.6221 9.66004 12.6329 9.85172 12.6546 10.1049C12.6763 10.358 12.6872 10.5497 12.6872 10.6799C12.7306 10.9548 12.7523 11.3382 12.7523 11.83C12.7523 13.1899 11.9855 13.8698 10.4521 13.8698H9.90958V12.7197H10.17C10.4882 12.7197 10.7161 12.6293 10.8535 12.4485C10.991 12.2676 11.0597 11.9964 11.0597 11.6347C11.0597 11.2731 11.038 10.9982 10.9946 10.8101C10.9946 10.6944 10.9801 10.5244 10.9512 10.3002C10.9222 10.0759 10.9078 9.90597 10.9078 9.79024C10.9078 9.25497 11.0235 8.84268 11.255 8.55334C11.4575 8.32188 11.7758 8.14828 12.2098 8.03255C11.7758 7.93128 11.4575 7.75768 11.255 7.51175C11.0235 7.23689 10.9078 6.82459 10.9078 6.27486C10.9078 6.1302 10.9222 5.92767 10.9512 5.66727C10.9801 5.4792 10.9946 5.34177 10.9946 5.25497C11.038 5.02351 11.0597 4.73418 11.0597 4.38698C11.0597 4.03978 10.9873 3.77939 10.8427 3.60579C10.698 3.43219 10.4738 3.32369 10.17 3.28029H9.90958V2H10.4521C11.1754 2 11.7396 2.16636 12.1447 2.49909C12.5497 2.83183 12.7523 3.34539 12.7523 4.03978C12.7523 4.16998 12.7414 4.36166 12.7197 4.61483C12.698 4.86799 12.6872 5.05967 12.6872 5.18987C12.6438 5.46474 12.6148 5.8481 12.6004 6.33996C12.6148 6.64376 12.6799 6.87523 12.7957 7.03436C12.9837 7.25136 13.3092 7.35986 13.7722 7.35986V8.50995Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
4
packages/core/src/browser/icons/expand.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
6
packages/core/src/browser/icons/loading-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 TypeFox and others.-->
|
||||
<!--Copied from https://raw.githubusercontent.com/microsoft/vscode-icons/9c90ce81b1f3c309000b80da0763aa09975a85f0/icons/dark/loading.svg -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0.75C8.17188 0.75 8.33333 0.783854 8.48438 0.851562C8.63542 0.914062 8.76823 1.0026 8.88281 1.11719C8.9974 1.23177 9.08594 1.36458 9.14844 1.51562C9.21615 1.66667 9.25 1.82812 9.25 2C9.25 2.17188 9.21615 2.33333 9.14844 2.48438C9.08594 2.63542 8.9974 2.76823 8.88281 2.88281C8.76823 2.9974 8.63542 3.08854 8.48438 3.15625C8.33333 3.21875 8.17188 3.25 8 3.25C7.82812 3.25 7.66667 3.21875 7.51562 3.15625C7.36458 3.08854 7.23177 2.9974 7.11719 2.88281C7.0026 2.76823 6.91146 2.63542 6.84375 2.48438C6.78125 2.33333 6.75 2.17188 6.75 2C6.75 1.82812 6.78125 1.66667 6.84375 1.51562C6.91146 1.36458 7.0026 1.23177 7.11719 1.11719C7.23177 1.0026 7.36458 0.914062 7.51562 0.851562C7.66667 0.783854 7.82812 0.75 8 0.75ZM2.63281 3.75781C2.63281 3.60156 2.66146 3.45573 2.71875 3.32031C2.77604 3.1849 2.85417 3.06771 2.95312 2.96875C3.05729 2.86458 3.17708 2.78385 3.3125 2.72656C3.45312 2.66406 3.60156 2.63281 3.75781 2.63281C3.91406 2.63281 4.0599 2.66406 4.19531 2.72656C4.33073 2.78385 4.44792 2.86458 4.54688 2.96875C4.65104 3.06771 4.73177 3.1849 4.78906 3.32031C4.85156 3.45573 4.88281 3.60156 4.88281 3.75781C4.88281 3.91406 4.85156 4.0625 4.78906 4.20312C4.73177 4.33854 4.65104 4.45833 4.54688 4.5625C4.44792 4.66146 4.33073 4.73958 4.19531 4.79688C4.0599 4.85417 3.91406 4.88281 3.75781 4.88281C3.60156 4.88281 3.45312 4.85417 3.3125 4.79688C3.17708 4.73958 3.05729 4.66146 2.95312 4.5625C2.85417 4.45833 2.77604 4.33854 2.71875 4.20312C2.66146 4.0625 2.63281 3.91406 2.63281 3.75781ZM2 7C2.14062 7 2.27083 7.02604 2.39062 7.07812C2.51042 7.13021 2.61458 7.20312 2.70312 7.29688C2.79688 7.38542 2.86979 7.48958 2.92188 7.60938C2.97396 7.72917 3 7.85938 3 8C3 8.14062 2.97396 8.27083 2.92188 8.39062C2.86979 8.51042 2.79688 8.61719 2.70312 8.71094C2.61458 8.79948 2.51042 8.86979 2.39062 8.92188C2.27083 8.97396 2.14062 9 2 9C1.85938 9 1.72917 8.97396 1.60938 8.92188C1.48958 8.86979 1.38281 8.79948 1.28906 8.71094C1.20052 8.61719 1.13021 8.51042 1.07812 8.39062C1.02604 8.27083 1 8.14062 1 8C1 7.85938 1.02604 7.72917 1.07812 7.60938C1.13021 7.48958 1.20052 7.38542 1.28906 7.29688C1.38281 7.20312 1.48958 7.13021 1.60938 7.07812C1.72917 7.02604 1.85938 7 2 7ZM2.88281 12.2422C2.88281 12.1224 2.90625 12.0104 2.95312 11.9062C3 11.7969 3.0625 11.7031 3.14062 11.625C3.21875 11.5469 3.3099 11.4844 3.41406 11.4375C3.52344 11.3906 3.63802 11.3672 3.75781 11.3672C3.8776 11.3672 3.98958 11.3906 4.09375 11.4375C4.20312 11.4844 4.29688 11.5469 4.375 11.625C4.45312 11.7031 4.51562 11.7969 4.5625 11.9062C4.60938 12.0104 4.63281 12.1224 4.63281 12.2422C4.63281 12.362 4.60938 12.4766 4.5625 12.5859C4.51562 12.6901 4.45312 12.7812 4.375 12.8594C4.29688 12.9375 4.20312 13 4.09375 13.0469C3.98958 13.0938 3.8776 13.1172 3.75781 13.1172C3.63802 13.1172 3.52344 13.0938 3.41406 13.0469C3.3099 13 3.21875 12.9375 3.14062 12.8594C3.0625 12.7812 3 12.6901 2.95312 12.5859C2.90625 12.4766 2.88281 12.362 2.88281 12.2422ZM8 13.25C8.20833 13.25 8.38542 13.3229 8.53125 13.4688C8.67708 13.6146 8.75 13.7917 8.75 14C8.75 14.2083 8.67708 14.3854 8.53125 14.5312C8.38542 14.6771 8.20833 14.75 8 14.75C7.79167 14.75 7.61458 14.6771 7.46875 14.5312C7.32292 14.3854 7.25 14.2083 7.25 14C7.25 13.7917 7.32292 13.6146 7.46875 13.4688C7.61458 13.3229 7.79167 13.25 8 13.25ZM11.6172 12.2422C11.6172 12.0651 11.6771 11.9167 11.7969 11.7969C11.9167 11.6771 12.0651 11.6172 12.2422 11.6172C12.4193 11.6172 12.5677 11.6771 12.6875 11.7969C12.8073 11.9167 12.8672 12.0651 12.8672 12.2422C12.8672 12.4193 12.8073 12.5677 12.6875 12.6875C12.5677 12.8073 12.4193 12.8672 12.2422 12.8672C12.0651 12.8672 11.9167 12.8073 11.7969 12.6875C11.6771 12.5677 11.6172 12.4193 11.6172 12.2422ZM14 7.5C14.1354 7.5 14.2526 7.54948 14.3516 7.64844C14.4505 7.7474 14.5 7.86458 14.5 8C14.5 8.13542 14.4505 8.2526 14.3516 8.35156C14.2526 8.45052 14.1354 8.5 14 8.5C13.8646 8.5 13.7474 8.45052 13.6484 8.35156C13.5495 8.2526 13.5 8.13542 13.5 8C13.5 7.86458 13.5495 7.7474 13.6484 7.64844C13.7474 7.54948 13.8646 7.5 14 7.5ZM12.2422 2.38281C12.4297 2.38281 12.6068 2.41927 12.7734 2.49219C12.9401 2.5651 13.0859 2.66406 13.2109 2.78906C13.3359 2.91406 13.4349 3.0599 13.5078 3.22656C13.5807 3.39323 13.6172 3.57031 13.6172 3.75781C13.6172 3.94531 13.5807 4.1224 13.5078 4.28906C13.4349 4.45573 13.3359 4.60156 13.2109 4.72656C13.0859 4.85156 12.9401 4.95052 12.7734 5.02344C12.6068 5.09635 12.4297 5.13281 12.2422 5.13281C12.0547 5.13281 11.8776 5.09635 11.7109 5.02344C11.5443 4.95052 11.3984 4.85156 11.2734 4.72656C11.1484 4.60156 11.0495 4.45573 10.9766 4.28906C10.9036 4.1224 10.8672 3.94531 10.8672 3.75781C10.8672 3.57031 10.9036 3.39323 10.9766 3.22656C11.0495 3.0599 11.1484 2.91406 11.2734 2.78906C11.3984 2.66406 11.5443 2.5651 11.7109 2.49219C11.8776 2.41927 12.0547 2.38281 12.2422 2.38281Z" fill="#C5C5C5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
6
packages/core/src/browser/icons/loading-light.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2020 TypeFox and others.-->
|
||||
<!--Copied from https://raw.githubusercontent.com/microsoft/vscode-icons/9c90ce81b1f3c309000b80da0763aa09975a85f0/icons/light/loading.svg -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0.75C8.17188 0.75 8.33333 0.783854 8.48438 0.851562C8.63542 0.914062 8.76823 1.0026 8.88281 1.11719C8.9974 1.23177 9.08594 1.36458 9.14844 1.51562C9.21615 1.66667 9.25 1.82812 9.25 2C9.25 2.17188 9.21615 2.33333 9.14844 2.48438C9.08594 2.63542 8.9974 2.76823 8.88281 2.88281C8.76823 2.9974 8.63542 3.08854 8.48438 3.15625C8.33333 3.21875 8.17188 3.25 8 3.25C7.82812 3.25 7.66667 3.21875 7.51562 3.15625C7.36458 3.08854 7.23177 2.9974 7.11719 2.88281C7.0026 2.76823 6.91146 2.63542 6.84375 2.48438C6.78125 2.33333 6.75 2.17188 6.75 2C6.75 1.82812 6.78125 1.66667 6.84375 1.51562C6.91146 1.36458 7.0026 1.23177 7.11719 1.11719C7.23177 1.0026 7.36458 0.914062 7.51562 0.851562C7.66667 0.783854 7.82812 0.75 8 0.75ZM2.63281 3.75781C2.63281 3.60156 2.66146 3.45573 2.71875 3.32031C2.77604 3.1849 2.85417 3.06771 2.95312 2.96875C3.05729 2.86458 3.17708 2.78385 3.3125 2.72656C3.45312 2.66406 3.60156 2.63281 3.75781 2.63281C3.91406 2.63281 4.0599 2.66406 4.19531 2.72656C4.33073 2.78385 4.44792 2.86458 4.54688 2.96875C4.65104 3.06771 4.73177 3.1849 4.78906 3.32031C4.85156 3.45573 4.88281 3.60156 4.88281 3.75781C4.88281 3.91406 4.85156 4.0625 4.78906 4.20312C4.73177 4.33854 4.65104 4.45833 4.54688 4.5625C4.44792 4.66146 4.33073 4.73958 4.19531 4.79688C4.0599 4.85417 3.91406 4.88281 3.75781 4.88281C3.60156 4.88281 3.45312 4.85417 3.3125 4.79688C3.17708 4.73958 3.05729 4.66146 2.95312 4.5625C2.85417 4.45833 2.77604 4.33854 2.71875 4.20312C2.66146 4.0625 2.63281 3.91406 2.63281 3.75781ZM2 7C2.14062 7 2.27083 7.02604 2.39062 7.07812C2.51042 7.13021 2.61458 7.20312 2.70312 7.29688C2.79688 7.38542 2.86979 7.48958 2.92188 7.60938C2.97396 7.72917 3 7.85938 3 8C3 8.14062 2.97396 8.27083 2.92188 8.39062C2.86979 8.51042 2.79688 8.61719 2.70312 8.71094C2.61458 8.79948 2.51042 8.86979 2.39062 8.92188C2.27083 8.97396 2.14062 9 2 9C1.85938 9 1.72917 8.97396 1.60938 8.92188C1.48958 8.86979 1.38281 8.79948 1.28906 8.71094C1.20052 8.61719 1.13021 8.51042 1.07812 8.39062C1.02604 8.27083 1 8.14062 1 8C1 7.85938 1.02604 7.72917 1.07812 7.60938C1.13021 7.48958 1.20052 7.38542 1.28906 7.29688C1.38281 7.20312 1.48958 7.13021 1.60938 7.07812C1.72917 7.02604 1.85938 7 2 7ZM2.88281 12.2422C2.88281 12.1224 2.90625 12.0104 2.95312 11.9062C3 11.7969 3.0625 11.7031 3.14062 11.625C3.21875 11.5469 3.3099 11.4844 3.41406 11.4375C3.52344 11.3906 3.63802 11.3672 3.75781 11.3672C3.8776 11.3672 3.98958 11.3906 4.09375 11.4375C4.20312 11.4844 4.29688 11.5469 4.375 11.625C4.45312 11.7031 4.51562 11.7969 4.5625 11.9062C4.60938 12.0104 4.63281 12.1224 4.63281 12.2422C4.63281 12.362 4.60938 12.4766 4.5625 12.5859C4.51562 12.6901 4.45312 12.7812 4.375 12.8594C4.29688 12.9375 4.20312 13 4.09375 13.0469C3.98958 13.0938 3.8776 13.1172 3.75781 13.1172C3.63802 13.1172 3.52344 13.0938 3.41406 13.0469C3.3099 13 3.21875 12.9375 3.14062 12.8594C3.0625 12.7812 3 12.6901 2.95312 12.5859C2.90625 12.4766 2.88281 12.362 2.88281 12.2422ZM8 13.25C8.20833 13.25 8.38542 13.3229 8.53125 13.4688C8.67708 13.6146 8.75 13.7917 8.75 14C8.75 14.2083 8.67708 14.3854 8.53125 14.5312C8.38542 14.6771 8.20833 14.75 8 14.75C7.79167 14.75 7.61458 14.6771 7.46875 14.5312C7.32292 14.3854 7.25 14.2083 7.25 14C7.25 13.7917 7.32292 13.6146 7.46875 13.4688C7.61458 13.3229 7.79167 13.25 8 13.25ZM11.6172 12.2422C11.6172 12.0651 11.6771 11.9167 11.7969 11.7969C11.9167 11.6771 12.0651 11.6172 12.2422 11.6172C12.4193 11.6172 12.5677 11.6771 12.6875 11.7969C12.8073 11.9167 12.8672 12.0651 12.8672 12.2422C12.8672 12.4193 12.8073 12.5677 12.6875 12.6875C12.5677 12.8073 12.4193 12.8672 12.2422 12.8672C12.0651 12.8672 11.9167 12.8073 11.7969 12.6875C11.6771 12.5677 11.6172 12.4193 11.6172 12.2422ZM14 7.5C14.1354 7.5 14.2526 7.54948 14.3516 7.64844C14.4505 7.7474 14.5 7.86458 14.5 8C14.5 8.13542 14.4505 8.2526 14.3516 8.35156C14.2526 8.45052 14.1354 8.5 14 8.5C13.8646 8.5 13.7474 8.45052 13.6484 8.35156C13.5495 8.2526 13.5 8.13542 13.5 8C13.5 7.86458 13.5495 7.7474 13.6484 7.64844C13.7474 7.54948 13.8646 7.5 14 7.5ZM12.2422 2.38281C12.4297 2.38281 12.6068 2.41927 12.7734 2.49219C12.9401 2.5651 13.0859 2.66406 13.2109 2.78906C13.3359 2.91406 13.4349 3.0599 13.5078 3.22656C13.5807 3.39323 13.6172 3.57031 13.6172 3.75781C13.6172 3.94531 13.5807 4.1224 13.5078 4.28906C13.4349 4.45573 13.3359 4.60156 13.2109 4.72656C13.0859 4.85156 12.9401 4.95052 12.7734 5.02344C12.6068 5.09635 12.4297 5.13281 12.2422 5.13281C12.0547 5.13281 11.8776 5.09635 11.7109 5.02344C11.5443 4.95052 11.3984 4.85156 11.2734 4.72656C11.1484 4.60156 11.0495 4.45573 10.9766 4.28906C10.9036 4.1224 10.8672 3.94531 10.8672 3.75781C10.8672 3.57031 10.9036 3.39323 10.9766 3.22656C11.0495 3.0599 11.1484 2.91406 11.2734 2.78906C11.3984 2.66406 11.5443 2.5651 11.7109 2.49219C11.8776 2.41927 12.0547 2.38281 12.2422 2.38281Z" fill="#424242" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
4
packages/core/src/browser/icons/open-change-bright.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m6 12h-1c-.27-.02-.48-.11-.69-.31s-.3-.42-.31-.69v-6.28c.59-.34 1-.98 1-1.72 0-1.11-.89-2-2-2s-2 .89-2 2c0 .73.41 1.38 1 1.72v6.28c.03.78.34 1.47.94 2.06s1.28.91 2.06.94c0 0 1.02 0 1 0v2l3-3-3-3zm-3-10.2c.66 0 1.2.55 1.2 1.2s-.55 1.2-1.2 1.2-1.2-.55-1.2-1.2.55-1.2 1.2-1.2zm11 9.48c0-1.73 0-6.28 0-6.28-.03-.78-.34-1.47-.94-2.06s-1.28-.91-2.06-.94h-1v-2l-3 3 3 3v-2h1c.27.02.48.11.69.31s.3.42.31.69v6.28c-.59.34-1 .98-1 1.72 0 1.11.89 2 2 2s2-.89 2-2c0-.73-.41-1.38-1-1.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2s.55-1.2 1.2-1.2 1.2.55 1.2 1.2-.55 1.2-1.2 1.2z" fill="#424242" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 931 B |
4
packages/core/src/browser/icons/open-change-dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m6 12h-1c-.27-.02-.48-.11-.69-.31s-.3-.42-.31-.69v-6.28c.59-.34 1-.98 1-1.72 0-1.11-.89-2-2-2s-2 .89-2 2c0 .73.41 1.38 1 1.72v6.28c.03.78.34 1.47.94 2.06s1.28.91 2.06.94c0 0 1.02 0 1 0v2l3-3-3-3zm-3-10.2c.66 0 1.2.55 1.2 1.2s-.55 1.2-1.2 1.2-1.2-.55-1.2-1.2.55-1.2 1.2-1.2zm11 9.48c0-1.73 0-6.28 0-6.28-.03-.78-.34-1.47-.94-2.06s-1.28-.91-2.06-.94h-1v-2l-3 3 3 3v-2h1c.27.02.48.11.69.31s.3.42.31.69v6.28c-.59.34-1 .98-1 1.72 0 1.11.89 2 2 2s2-.89 2-2c0-.73-.41-1.38-1-1.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2s.55-1.2 1.2-1.2 1.2.55 1.2 1.2-.55 1.2-1.2 1.2z" fill="#c5c5c5" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 932 B |
4
packages/core/src/browser/icons/open-file-bright.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#656565" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#00539C" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>
|
||||
|
After Width: | Height: | Size: 474 B |
4
packages/core/src/browser/icons/open-file-dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#C5C5C5" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#75BEFF" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>
|
||||
|
After Width: | Height: | Size: 474 B |
4
packages/core/src/browser/icons/preview-bright.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>PreviewInRightPanel_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H10.121l-.56.561a1.5,1.5,0,0,1-2.122,0A1.469,1.469,0,0,1,7.1,15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H7.1a1.469,1.469,0,0,1,.341-.561L9,11.88V5h5V7.351a3.515,3.515,0,0,1,1,.707V2ZM7,13H2V5H7Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M12.5,8a2.5,2.5,0,0,0-2.084,3.877l-2.27,2.269a.5.5,0,0,0,.708.708l2.269-2.27A2.5,2.5,0,1,0,12.5,8Zm0,4A1.5,1.5,0,1,1,14,10.5,1.5,1.5,0,0,1,12.5,12Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
packages/core/src/browser/icons/preview-dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2019 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>PreviewInRightPanel_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H10.121l-.56.561a1.5,1.5,0,0,1-2.122,0A1.469,1.469,0,0,1,7.1,15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H7.1a1.469,1.469,0,0,1,.341-.561L9,11.88V5h5V7.351a3.515,3.515,0,0,1,1,.707V2ZM7,13H2V5H7Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M12.5,8a2.5,2.5,0,0,0-2.084,3.877l-2.27,2.269a.5.5,0,0,0,.708.708l2.269-2.27A2.5,2.5,0,1,0,12.5,8Zm0,4A1.5,1.5,0,1,1,14,10.5,1.5,1.5,0,0,1,12.5,12Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
10
packages/core/src/browser/icons/regex-dark.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<polygon fill="#2d2d30" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/>
|
||||
<g fill="#C5C5C5">
|
||||
<path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/>
|
||||
<rect width="3" height="3" x="3" y="10"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 810 B |
10
packages/core/src/browser/icons/regex.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<polygon fill="#F6F6F6" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/>
|
||||
<g fill="#424242">
|
||||
<path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/>
|
||||
<rect width="3" height="3" x="3" y="10"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 810 B |
4
packages/core/src/browser/icons/remove-all-inverse.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>remove-all</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V12H14v2H12v2H0V5H2V3H4V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15,2v9H14V3H5V2ZM3,5h9v8h1V4H3ZM1,6H11v9H1Zm4.116,4.5L3.058,12.558l.884.884L6,11.384l2.058,2.058.884-.884L6.884,10.5,8.942,8.442l-.884-.884L6,9.616,3.942,7.558l-.884.884Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 848 B |
4
packages/core/src/browser/icons/remove-all.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-fg{fill:#f0eff1;}</style></defs><title>remove-all_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,1V12H14v2H12v2H0V5H2V3H4V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M3,4H13v9H12V5H3ZM1,15H11V6H1ZM5,2V3h9v8h1V2Z"/></g><g id="iconFg"><path class="icon-vs-fg" d="M6.75,10.75,9,13H7.5L6,11.5,4.5,13H3l2.25-2.25L3,8.5H4.5L6,10,7.5,8.5H9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 851 B |
13
packages/core/src/browser/icons/replace-all-inverse.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" x="0px" y="0px" enable-background="new 0 0 16 16" version="1.1" viewBox="0 0 16 16" xml:space="preserve">
|
||||
<g id="icon_x5F_bg">
|
||||
<path fill="#C5C5C5" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
|
||||
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
<path fill="#75BEFF" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
|
||||
C2.651,2,1.979,2.673,1.979,3.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
13
packages/core/src/browser/icons/replace-all.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" x="0px" y="0px" enable-background="new 0 0 16 16" version="1.1" viewBox="0 0 16 16" xml:space="preserve">
|
||||
<g id="icon_x5F_bg">
|
||||
<path fill="#424242" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
|
||||
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
<path fill="#00539C" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
|
||||
C2.651,2,1.979,2.673,1.979,3.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
15
packages/core/src/browser/icons/replace-inverse.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" x="0px" y="0px" enable-background="new 0 0 16 16" version="1.1" viewBox="0 0 16 16" xml:space="preserve">
|
||||
<g id="icon_x5F_bg">
|
||||
<g>
|
||||
<path fill="#C5C5C5" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
|
||||
<path fill="#C5C5C5" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="color_x5F_importance">
|
||||
<path fill="#75BEFF" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
|
||||
C4.651,2,3.979,2.673,3.979,3.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
15
packages/core/src/browser/icons/replace.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--Copyright (c) Microsoft Corporation. All rights reserved.-->
|
||||
<!--Copyright (C) 2018 TypeFox and others.-->
|
||||
<!--Licensed under the MIT License. See License.txt in the project root for license information.-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" x="0px" y="0px" enable-background="new 0 0 16 16" version="1.1" viewBox="0 0 16 16" xml:space="preserve">
|
||||
<g id="icon_x5F_bg">
|
||||
<g>
|
||||
<path fill="#424242" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
|
||||
<path fill="#424242" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="color_x5F_importance">
|
||||
<path fill="#00539C" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
|
||||
C4.651,2,3.979,2.673,3.979,3.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
BIN
packages/core/src/browser/icons/spinner.gif
Normal file
|
After Width: | Height: | Size: 43 KiB |