drag&drop a VSIX file on the extensions viewlet (#96907)

Install extensions though drag&drop on the extensions viewlet
This commit is contained in:
Ladislau Szomoru 2020-05-07 15:05:05 +02:00 committed by GitHub
parent ee8a6bc4f8
commit 01d0b18e5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 34 deletions

View file

@ -1608,11 +1608,11 @@ export class ShowInstalledExtensionsAction extends Action {
super(id, label, undefined, true);
}
run(): Promise<void> {
run(refresh?: boolean): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search('@installed ');
viewlet.search('@installed ', refresh);
viewlet.focus();
});
}
@ -2966,37 +2966,40 @@ export class InstallVSIXAction extends Action {
super(id, label, 'extension-action install-vsix', true);
}
run(): Promise<any> {
return Promise.resolve(this.fileDialogService.showOpenDialog({
title: localize('installFromVSIX', "Install from VSIX"),
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
canSelectFiles: true,
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
})).then(result => {
if (!result) {
return Promise.resolve();
}
async run(vsixPaths?: URI[]): Promise<void> {
if (!vsixPaths) {
vsixPaths = await this.fileDialogService.showOpenDialog({
title: localize('installFromVSIX', "Install from VSIX"),
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
canSelectFiles: true,
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
});
return Promise.all(result.map(vsix => this.extensionsWorkbenchService.install(vsix)))
.then(extensions => {
for (const extension of extensions) {
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name)
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name);
const actions = requireReload ? [{
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
run: () => this.hostService.reload()
}] : [];
this.notificationService.prompt(
Severity.Info,
message,
actions,
{ sticky: true }
);
}
return this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run();
});
});
if (!vsixPaths) {
return;
}
}
// Install extension(s), display notification(s), display @installed extensions
await Promise.all(vsixPaths.map(async (vsix) => await this.extensionsWorkbenchService.install(vsix)))
.then(async (extensions) => {
for (const extension of extensions) {
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name)
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name);
const actions = requireReload ? [{
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
run: () => this.hostService.reload()
}] : [];
this.notificationService.prompt(
Severity.Info,
message,
actions,
{ sticky: true }
);
}
await this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run(true);
});
}
}

View file

@ -14,7 +14,7 @@ import { IAction, Action } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -57,6 +57,9 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { MementoObject } from 'vs/workbench/common/memento';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { URI } from 'vs/base/common/uri';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
@ -396,8 +399,12 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
addClass(parent, 'extensions-viewlet');
this.root = parent;
const header = append(this.root, $('.header'));
const overlay = append(this.root, $('.overlay'));
const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
overlay.style.backgroundColor = overlayBackgroundColor;
hide(overlay);
const header = append(this.root, $('.header'));
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
@ -431,6 +438,49 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
}
}));
// Register DragAndDrop support
this._register(new DragAndDropObserver(this.root, {
onDragEnd: (e: DragEvent) => undefined,
onDragEnter: (e: DragEvent) => {
if (this.isSupportedDragElement(e)) {
show(overlay);
}
},
onDragLeave: (e: DragEvent) => {
if (this.isSupportedDragElement(e)) {
hide(overlay);
}
},
onDragOver: (e: DragEvent) => {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = this.isSupportedDragElement(e) ? 'copy' : 'none';
}
},
onDrop: async (e: DragEvent) => {
if (this.isSupportedDragElement(e)) {
hide(overlay);
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
let vsixPaths: URI[] = [];
for (let index = 0; index < e.dataTransfer.files.length; index++) {
const path = e.dataTransfer.files.item(index)!.path;
if (path.indexOf('.vsix') !== -1) {
vsixPaths.push(URI.parse(path));
}
}
try {
// Attempt to install the extension(s)
await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
}
catch (err) {
this.notificationService.error(err);
}
}
}
},
}));
super.create(append(this.root, $('.extensions')));
}
@ -617,6 +667,15 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.notificationService.error(err);
}
private isSupportedDragElement(e: DragEvent): boolean {
if (e.dataTransfer) {
const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
return typesLowerCase.indexOf('files') !== -1;
}
return false;
}
}
export class StatusUpdater extends Disposable implements IWorkbenchContribution {

View file

@ -4,9 +4,19 @@
*--------------------------------------------------------------------------------------------*/
.extensions-viewlet {
position: relative;
height: 100%;
}
.extensions-viewlet > .overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
}
.extensions-viewlet > .header {
height: 41px;
box-sizing: border-box;