Skip to content

Commit

Permalink
fix eclipse-theia#4088: Add 'Upload Files...' menu entries
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>

Signed-off-by: Doron Nahari doron.nahari@sap.com
  • Loading branch information
akosyakov committed Mar 29, 2019
1 parent d2b5a24 commit 590afe0
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 39 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## v0.6.0

- [filesystem] added the menu item `Upload Files...` to easily upload files into a workspace

## v0.5.0

- Added `scope` to task configurations to differentiate 3 things: task type, task source, and where to run tasks
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/browser/tree/tree-expansion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ export interface ExpandableTreeNode extends CompositeTreeNode {
}

export namespace ExpandableTreeNode {
export function is(node: TreeNode | undefined): node is ExpandableTreeNode {
export function is(node: Object | undefined): node is ExpandableTreeNode {
return !!node && CompositeTreeNode.is(node) && 'expanded' in node;
}

export function isExpanded(node: TreeNode | undefined): node is ExpandableTreeNode {
export function isExpanded(node: Object | undefined): node is ExpandableTreeNode {
return ExpandableTreeNode.is(node) && node.expanded;
}

export function isCollapsed(node: TreeNode | undefined): node is ExpandableTreeNode {
export function isCollapsed(node: Object | undefined): node is ExpandableTreeNode {
return ExpandableTreeNode.is(node) && !node.expanded;
}
}
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/browser/tree/tree-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,6 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {

protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
if (this.props.globalSelection) {
this.updateGlobalSelection();
}
this.node.focus();
if (this.model.selectedNodes.length === 0) {
const root = this.model.root;
Expand All @@ -287,6 +284,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
}
}
}
// it has to be called after nodes are selected
if (this.props.globalSelection) {
this.updateGlobalSelection();
}
this.forceUpdate();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/browser/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export interface CompositeTreeNode extends TreeNode {
}

export namespace CompositeTreeNode {
export function is(node: TreeNode | undefined): node is CompositeTreeNode {
export function is(node: Object | undefined): node is CompositeTreeNode {
return !!node && 'children' in node;
}

Expand Down
101 changes: 101 additions & 0 deletions packages/core/src/common/selection-command-handler.ts
Original file line number Diff line number Diff line change
@@ -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 WITH Classpath-exception-2.0
********************************************************************************/

// tslint:disable:no-any
import { CommandHandler } from './command';
import { SelectionService } from '../common/selection-service';

export class SelectionCommandHandler<S> implements CommandHandler {

constructor(
protected readonly selectionService: SelectionService,
protected readonly toSelection: (arg: any) => S | undefined,
protected readonly options: SelectionCommandHandler.Options<S>
) { }

execute(...args: any[]): Object | undefined {
const selection = this.getSelection(...args);
return selection ? (this.options.execute as any)(selection, ...args) : undefined;
}

isVisible(...args: any[]): boolean {
const selection = this.getSelection(...args);
return !!selection && (!this.options.isVisible || (this.options.isVisible as any)(selection as any, ...args));
}

isEnabled(...args: any[]): boolean {
const selection = this.getSelection(...args);
return !!selection && (!this.options.isEnabled || (this.options.isEnabled as any)(selection as any, ...args));
}

protected isMulti(): boolean {
return this.options && !!this.options.multi;
}

protected getSelection(...args: any[]): S | S[] | undefined {
const givenSelection = args.length && this.toSelection(args[0]);
if (givenSelection) {
return this.isMulti() ? [givenSelection] : givenSelection;
}
const globalSelection = this.getSingleSelection(this.selectionService.selection);
if (this.isMulti()) {
return this.getMulitSelection(globalSelection);
}
return this.getSingleSelection(globalSelection);
}

protected getSingleSelection(arg: Object | undefined): S | undefined {
let selection = this.toSelection(arg);
if (selection) {
return selection;
}
if (Array.isArray(arg)) {
for (const element of arg) {
selection = this.toSelection(element);
if (selection) {
return selection;
}
}
}
return undefined;
}

protected getMulitSelection(arg: Object | undefined): S[] | undefined {
let selection = this.toSelection(arg);
if (selection) {
return [selection];
}
const result = [];
if (Array.isArray(arg)) {
for (const element of arg) {
selection = this.toSelection(element);
if (selection) {
result.push(selection);
}
}
}
return result.length ? result : undefined;
}
}
export namespace SelectionCommandHandler {
export type Options<S> = SelectionOptions<false, S> | SelectionOptions<true, S[]>;
export interface SelectionOptions<Multi extends boolean, T> {
multi: Multi;
execute(selection: T, ...args: any[]): any;
isEnabled?(selection: T, ...args: any[]): boolean;
isVisible?(selection: T, ...args: any[]): boolean;
}
}
2 changes: 2 additions & 0 deletions packages/filesystem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@theia/core": "^0.5.0",
"@types/base64-js": "^1.2.5",
"@types/body-parser": "^1.17.0",
"@types/formidable": "^1.0.31",
"@types/fs-extra": "^4.0.2",
"@types/mime-types": "^2.1.0",
"@types/rimraf": "^2.0.2",
Expand All @@ -15,6 +16,7 @@
"base64-js": "^1.2.1",
"body-parser": "^1.18.3",
"drivelist": "^6.4.3",
"formidable": "^1.2.1",
"fs-extra": "^4.0.2",
"http-status-codes": "^1.3.0",
"mime-types": "^2.1.18",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

import { inject, injectable } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { notEmpty } from '@theia/core/lib/common/objects';
import { UriSelection } from '@theia/core/lib/common/selection';
import { SelectionService } from '@theia/core/lib/common/selection-service';
import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
import { UriAwareCommandHandler, UriCommandHandler } from '@theia/core/lib/common/uri-command-handler';
import { ExpandableTreeNode } from '@theia/core/lib/browser/tree';
import { FileDownloadService } from './file-download-service';
import { FileSelection } from '../file-selection';
import { TreeWidgetSelection } from '@theia/core/lib/browser/tree/tree-widget-selection';
import { isCancelled } from '@theia/core/lib/common/cancellation';

@injectable()
export class FileDownloadCommandContribution implements CommandContribution {
Expand All @@ -35,6 +37,30 @@ export class FileDownloadCommandContribution implements CommandContribution {
registerCommands(registry: CommandRegistry): void {
const handler = new UriAwareCommandHandler<URI[]>(this.selectionService, this.downloadHandler(), { multi: true });
registry.registerCommand(FileDownloadCommands.DOWNLOAD, handler);
registry.registerCommand(FileDownloadCommands.UPLOAD, new FileSelection.CommandHandler(this.selectionService, {
multi: false,
isEnabled: selection => this.canUpload(selection),
isVisible: selection => this.canUpload(selection),
execute: selection => this.upload(selection)
}));
}

protected canUpload({ fileStat }: FileSelection): boolean {
return fileStat.isDirectory;
}

protected async upload(selection: FileSelection): Promise<void> {
try {
const source = TreeWidgetSelection.getSource(this.selectionService.selection);
await this.downloadService.upload(selection.fileStat.uri);
if (ExpandableTreeNode.is(selection) && source) {
await source.model.expandNode(selection);
}
} catch (e) {
if (!isCancelled(e)) {
console.error(e);
}
}
}

protected downloadHandler(): UriCommandHandler<URI[]> {
Expand All @@ -57,29 +83,20 @@ export class FileDownloadCommandContribution implements CommandContribution {
return this.isDownloadEnabled(uris);
}

protected getUris(uri: Object | undefined): URI[] {
if (uri === undefined) {
return [];
}
return (Array.isArray(uri) ? uri : [uri]).map(u => this.getUri(u)).filter(notEmpty);
}

protected getUri(uri: Object | undefined): URI | undefined {
if (uri instanceof URI) {
return uri;
}
if (UriSelection.is(uri)) {
return uri.uri;
}
return undefined;
}

}

export namespace FileDownloadCommands {

export const DOWNLOAD: Command = {
id: 'file.download'
id: 'file.download',
category: 'File',
label: 'Download'
};

export const UPLOAD: Command = {
id: 'file.upload',
category: 'File',
label: 'Upload Files...'
};

}
Loading

0 comments on commit 590afe0

Please sign in to comment.