Skip to content

Commit

Permalink
web - allow to download folders (fix microsoft#83579)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero authored and jmannanc committed Oct 26, 2020
1 parent 1b84d77 commit 7de9297
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 49 deletions.
45 changes: 45 additions & 0 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1392,3 +1392,48 @@ function toBinary(str: string): string {
export function multibyteAwareBtoa(str: string): string {
return btoa(toBinary(str));
}

/**
* Typings for the https://wicg.github.io/file-system-access
*
* Use `supported(window)` to find out if the browser supports this kind of API.
*/
export namespace WebFileSystemAccess {

// https://wicg.github.io/file-system-access/#dom-window-showdirectorypicker
export interface FileSystemAccess {
showDirectoryPicker: () => Promise<FileSystemDirectoryHandle>;
}

// https://wicg.github.io/file-system-access/#api-filesystemdirectoryhandle
export interface FileSystemDirectoryHandle {
readonly kind: 'directory',
readonly name: string,

getFileHandle: (name: string, options?: { create?: boolean }) => Promise<FileSystemFileHandle>;
getDirectoryHandle: (name: string, options?: { create?: boolean }) => Promise<FileSystemDirectoryHandle>;
}

// https://wicg.github.io/file-system-access/#api-filesystemfilehandle
export interface FileSystemFileHandle {
readonly kind: 'file',
readonly name: string,

createWritable: (options?: { keepExistingData?: boolean }) => Promise<FileSystemWritableFileStream>;
}

// https://wicg.github.io/file-system-access/#api-filesystemwritablefilestream
export interface FileSystemWritableFileStream {
write: (buffer: Uint8Array) => Promise<void>;
close: () => Promise<void>;
}

export function supported(obj: any & Window): obj is FileSystemAccess {
const candidate = obj as FileSystemAccess;
if (typeof candidate?.showDirectoryPicker === 'function') {
return true;
}

return false;
}
}
8 changes: 6 additions & 2 deletions src/vs/platform/files/common/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { Event } from 'vs/base/common/event';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { isNumber, isUndefinedOrNull } from 'vs/base/common/types';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { ReadableStreamEvents } from 'vs/base/common/stream';
import { CancellationToken } from 'vs/base/common/cancellation';
Expand Down Expand Up @@ -978,8 +978,12 @@ export class BinarySize {
static readonly TB = BinarySize.GB * BinarySize.KB;

static formatSize(size: number): string {
if (!isNumber(size)) {
size = 0;
}

if (size < BinarySize.KB) {
return localize('sizeB', "{0}B", size);
return localize('sizeB', "{0}B", size.toFixed(0));
}

if (size < BinarySize.MB) {
Expand Down
8 changes: 7 additions & 1 deletion src/vs/workbench/browser/contextkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys';
import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { trackFocus, addDisposableListener, EventType, WebFileSystemAccess } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
Expand All @@ -32,6 +32,9 @@ export const RemoteNameContext = new RawContextKey<string>('remoteName', '');

export const IsFullscreenContext = new RawContextKey<boolean>('isFullscreen', false);

// Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
export const HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false);

export class WorkbenchContextKeysHandler extends Disposable {
private inputFocusedContext: IContextKey<boolean>;

Expand Down Expand Up @@ -85,6 +88,9 @@ export class WorkbenchContextKeysHandler extends Disposable {

RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.remoteAuthority) || '');

// Capabilities
HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window));

// Development
IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment);

Expand Down
13 changes: 10 additions & 3 deletions src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfi
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService';
import { Schemas } from 'vs/base/common/network';
import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys';
import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, HasWebFileSystemAccess, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
Expand Down Expand Up @@ -222,7 +222,7 @@ appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('com
appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category);
appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0'));
appendToCommandPalette(NEW_FOLDER_COMMAND_ID, { value: NEW_FOLDER_LABEL, original: 'New Folder' }, category, WorkspaceFolderCountContext.notEqualsTo('0'));
appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: DOWNLOAD_LABEL, original: 'Download' }, category, ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file)));
appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: DOWNLOAD_LABEL, original: 'Download...' }, category, ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file)));
appendToCommandPalette(NEW_UNTITLED_FILE_COMMAND_ID, { value: NEW_UNTITLED_FILE_LABEL, original: 'New Untitled File' }, category);

// Menu registration - open editors
Expand Down Expand Up @@ -489,7 +489,14 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({
id: DOWNLOAD_COMMAND_ID,
title: DOWNLOAD_LABEL,
},
when: ContextKeyExpr.or(ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), IsWebContext.toNegated()), ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()))
when: ContextKeyExpr.or(
// native: for any remote resource
ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)),
// web: for any files
ContextKeyExpr.and(IsWebContext, ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()),
// web: for any folders if file system API support is provided
ContextKeyExpr.and(IsWebContext, HasWebFileSystemAccess)
)
}));

MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
Expand Down
Loading

0 comments on commit 7de9297

Please sign in to comment.