提交 1a54aa89 编写于 作者: B Benjamin Pasero

implement data editor input and hook up to resource viewer

上级 7f0b0e17
......@@ -14,7 +14,7 @@ import { Builder, $ } from 'vs/base/browser/builder';
import DOM = require('vs/base/browser/dom');
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { BoundedMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
interface MapExtToMediaMimes {
[index: string]: string;
......@@ -75,6 +75,7 @@ export interface IResourceDescriptor {
name: string;
size: number;
etag: string;
mime: string;
}
// Chrome is caching images very aggressively and so we use the ETag information to find out if
......@@ -83,6 +84,10 @@ export interface IResourceDescriptor {
// memory grows (see also https://github.com/electron/electron/issues/6275)
const IMAGE_RESOURCE_ETAG_CACHE = new BoundedMap<{ etag: string, src: string }>(100);
function imageSrc(descriptor: IResourceDescriptor): string {
if (descriptor.resource.scheme === Schemas.data) {
return descriptor.resource.toString(true /* skip encoding */);
}
const src = descriptor.resource.toString();
let cached = IMAGE_RESOURCE_ETAG_CACHE.get(src);
......@@ -119,23 +124,26 @@ export class ResourceViewer {
openExternal: (uri: URI) => void,
metadataClb?: (meta: string) => void
): void {
// Ensure CSS class
$(container).setClass('monaco-resource-viewer');
// Lookup media mime if any
let mime: string;
const ext = paths.extname(descriptor.resource.toString());
if (ext) {
mime = mapExtToMediaMimes[ext.toLowerCase()];
let mime = descriptor.mime;
if (!mime && descriptor.resource.scheme === Schemas.file) {
const ext = paths.extname(descriptor.resource.toString());
if (ext) {
mime = mapExtToMediaMimes[ext.toLowerCase()];
}
}
if (!mime) {
mime = mimes.MIME_BINARY;
}
// Show Image inline
// Show Image inline unless they are large
if (mime.indexOf('image/') >= 0) {
if (descriptor.size <= ResourceViewer.MAX_IMAGE_SIZE) {
if (ResourceViewer.inlineImage(descriptor)) {
$(container)
.empty()
.addClass('image')
......@@ -159,18 +167,21 @@ export class ResourceViewer {
scrollbar.scanDomNode();
});
} else {
$(container)
const imageContainer = $(container)
.empty()
.p({
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
})
.append($('a', {
});
if (descriptor.resource.scheme !== Schemas.data) {
imageContainer.append($('a', {
role: 'button',
class: 'open-external',
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
}).on(DOM.EventType.CLICK, (e) => {
openExternal(descriptor.resource);
}));
}
}
}
......@@ -190,6 +201,26 @@ export class ResourceViewer {
}
}
private static inlineImage(descriptor: IResourceDescriptor): boolean {
let skipInlineImage: boolean;
// Data URI
if (descriptor.resource.scheme === Schemas.data) {
const BASE64_MARKER = 'base64,';
const base64MarkerIndex = descriptor.resource.path.indexOf(BASE64_MARKER);
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + BASE64_MARKER.length).length > 0;
skipInlineImage = !hasData || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE || descriptor.resource.path.length > ResourceViewer.MAX_IMAGE_SIZE;
}
// File URI
else {
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE;
}
return !skipInlineImage;
}
private static formatSize(size: number): string {
if (size < ResourceViewer.KB) {
return nls.localize('sizeB', "{0}B", size);
......
......@@ -41,4 +41,6 @@ export namespace Schemas {
export const mailto: string = 'mailto';
export const untitled: string = 'untitled';
export const data: string = 'data';
}
......@@ -88,7 +88,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Render Input
const model = <BinaryEditorModel>resolvedModel;
ResourceViewer.show(
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
this.binaryContainer,
this.scrollbar,
(resource: URI) => {
......
......@@ -8,6 +8,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { EditorModel } from 'vs/workbench/common/editor';
import URI from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { DataUri } from 'vs/workbench/common/resources';
/**
* An editor model that just represents a resource that can be loaded.
......@@ -17,6 +19,7 @@ export class BinaryEditorModel extends EditorModel {
private resource: URI;
private size: number;
private etag: string;
private mime: string;
constructor(
resource: URI,
......@@ -25,8 +28,17 @@ export class BinaryEditorModel extends EditorModel {
) {
super();
this.name = name;
this.resource = resource;
this.name = name;
if (resource.scheme === Schemas.data) {
const metadata = DataUri.parseMetaData(resource);
if (metadata.has(DataUri.META_DATA_SIZE)) {
this.size = Number(metadata.get(DataUri.META_DATA_SIZE));
}
this.mime = metadata.get(DataUri.META_DATA_MIME);
}
}
/**
......@@ -44,25 +56,38 @@ export class BinaryEditorModel extends EditorModel {
}
/**
* The size of the binary file if known.
* The size of the binary resource if known.
*/
public getSize(): number {
return this.size;
}
/**
* The etag of the binary file if known.
* The mime of the binary resource if known.
*/
public getMime(): string {
return this.mime;
}
/**
* The etag of the binary resource if known.
*/
public getETag(): string {
return this.etag;
}
public load(): TPromise<EditorModel> {
return this.fileService.resolveFile(this.resource).then(stat => {
this.etag = stat.etag;
this.size = stat.size;
return this;
});
// Make sure to resolve up to date stat for file resources
if (this.fileService.canHandleResource(this.resource)) {
return this.fileService.resolveFile(this.resource).then(stat => {
this.etag = stat.etag;
this.size = stat.size;
return this;
});
}
return TPromise.wrap(this);
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor';
import URI from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { DataUri } from 'vs/workbench/common/resources';
/**
* An editor input to present data URIs in a binary editor. Data URIs have the form of:
* data:[mime type];[meta data <key=value>;...];base64,[base64 encoded value]
*/
export class DataUriEditorInput extends EditorInput {
static ID: string = 'workbench.editors.dataUriEditorInput';
private resource: URI;
private name: string;
private description: string;
constructor(
name: string,
description: string,
resource: URI,
@IInstantiationService private instantiationService: IInstantiationService
) {
super();
this.name = name;
this.description = description;
this.resource = resource;
if (!this.name || !this.description) {
const metadata = DataUri.parseMetaData(this.resource);
if (!this.name) {
this.name = metadata.get(DataUri.META_DATA_LABEL);
}
if (!this.description) {
this.description = metadata.get(DataUri.META_DATA_DESCRIPTION);
}
}
}
public getResource(): URI {
return this.resource;
}
public getTypeId(): string {
return DataUriEditorInput.ID;
}
public getName(): string {
return this.name;
}
public getDescription(): string {
return this.description;
}
public resolve(refresh?: boolean): TPromise<BinaryEditorModel> {
return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load().then(m => m as BinaryEditorModel);
}
public matches(otherInput: any): boolean {
if (super.matches(otherInput) === true) {
return true;
}
if (otherInput instanceof DataUriEditorInput) {
const otherDataUriEditorInput = <DataUriEditorInput>otherInput;
// Compare by resource
return otherDataUriEditorInput.resource.toString() === this.resource.toString();
}
return false;
}
}
......@@ -174,3 +174,37 @@ export class ResourceGlobMatcher {
this.toUnbind = dispose(this.toUnbind);
}
}
/**
* Data URI related helpers.
*/
export namespace DataUri {
export const META_DATA_LABEL = 'label';
export const META_DATA_DESCRIPTION = 'description';
export const META_DATA_SIZE = 'size';
export const META_DATA_MIME = 'mime';
export function parseMetaData(dataUri: URI): Map<string, string> {
const metadata = new Map<string, string>();
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
// the metadata is: size:2313;label:SomeLabel;description:SomeDescription
const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';'));
meta.split(';').forEach(property => {
const [key, value] = property.split(':');
if (key && value) {
metadata.set(key, value);
}
});
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
// the mime is: image/png
const mime = dataUri.path.substring(0, dataUri.path.indexOf(';'));
if (mime) {
metadata.set(META_DATA_MIME, mime);
}
return metadata;
}
}
......@@ -31,6 +31,7 @@ import * as platform from 'vs/base/common/platform';
import { DirtyFilesTracker } from 'vs/workbench/parts/files/common/dirtyFilesTracker';
import { ExplorerViewlet } from 'vs/workbench/parts/files/browser/explorerViewlet';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput';
// Viewlet Action
export class OpenExplorerViewletAction extends ToggleViewletAction {
......@@ -89,7 +90,8 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
nls.localize('binaryFileEditor', "Binary File Editor")
),
[
new SyncDescriptor<EditorInput>(FileEditorInput)
new SyncDescriptor<EditorInput>(FileEditorInput),
new SyncDescriptor<EditorInput>(DataUriEditorInput)
]
);
......
......@@ -265,9 +265,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
}
private resolveAsBinary(): TPromise<BinaryEditorModel> {
return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName())
.load()
.then(x => x as BinaryEditorModel);
return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load().then(m => m as BinaryEditorModel);
}
public isResolved(): boolean {
......
......@@ -23,6 +23,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { once } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput';
export const IWorkbenchEditorService = createDecorator<IWorkbenchEditorService>('editorService');
......@@ -121,7 +122,7 @@ export interface IEditorPart {
getActiveEditorInput(): IEditorInput;
}
type ICachedEditorInput = ResourceEditorInput | IFileEditorInput;
type ICachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput;
export class WorkbenchEditorService implements IWorkbenchEditorService {
......@@ -323,8 +324,8 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
const resourceInput = <IResourceInput>input;
// Files support
if (resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.file) {
// Files / Data URI support
if (resourceInput.resource instanceof URI && (resourceInput.resource.scheme === network.Schemas.file || resourceInput.resource.scheme === network.Schemas.data)) {
return this.createOrGet(resourceInput.resource, this.instantiationService, resourceInput.label, resourceInput.description, resourceInput.encoding);
}
......@@ -350,7 +351,7 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
if (input instanceof ResourceEditorInput) {
input.setName(label);
input.setDescription(description);
} else {
} else if (!(input instanceof DataUriEditorInput)) {
input.setPreferredEncoding(encoding);
}
......@@ -358,9 +359,19 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
}
let input: ICachedEditorInput;
if (resource.scheme === network.Schemas.file || this.fileService.canHandleResource && this.fileService.canHandleResource(resource)) {
// File
if (resource.scheme === network.Schemas.file || this.fileService.canHandleResource(resource)) {
input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService);
} else {
}
// Data URI
else if (resource.scheme === network.Schemas.data) {
input = instantiationService.createInstance(DataUriEditorInput, label, description, resource);
}
// Resource
else {
input = instantiationService.createInstance(ResourceEditorInput, label, description, resource);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices';
import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
suite('Workbench - DataUriEditorInput', () => {
let instantiationService: IInstantiationService;
setup(() => {
instantiationService = workbenchInstantiationService();
});
test('simple', function () {
const resource = URI.parse('data:image/png;label:SomeLabel;description:SomeDescription;size:1024;base64,77+9UE5');
const input: DataUriEditorInput = instantiationService.createInstance(DataUriEditorInput, void 0, void 0, resource);
assert.equal(input.getName(), 'SomeLabel');
assert.equal(input.getDescription(), 'SomeDescription');
return input.resolve().then((model: BinaryEditorModel) => {
assert.ok(model);
assert.equal(model.getSize(), 1024);
assert.equal(model.getMime(), 'image/png');
});
});
});
\ No newline at end of file
......@@ -796,6 +796,10 @@ export class TestFileService implements IFileService {
return TPromise.as(null);
}
canHandleResource(resource: URI): boolean {
return resource.scheme === 'file';
}
del(resource: URI, useTrash?: boolean): TPromise<void> {
return TPromise.as(null);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册