Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 (#7206)

* Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463

* fix config changes

* fix strictnull checks
This commit is contained in:
Anthony Dresser
2019-09-15 22:38:26 -07:00
committed by GitHub
parent fa6c52699e
commit ea0f9e6ce9
1226 changed files with 21541 additions and 17633 deletions

View File

@@ -0,0 +1,10 @@
test/**
src/**
tsconfig.json
out/test/**
out/**
extension.webpack.config.js
cgmanifest.json
yarn.lock
preview-src/**
webpack.config.js

View File

@@ -0,0 +1,3 @@
# Image Preview
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
resolve: {
mainFields: ['module', 'main']
},
entry: {
extension: './src/extension.ts',
}
});

View File

@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
html, body {
height: 100%;
max-height: 100%;
}
body img {
max-width: none;
max-height: none;
}
.container:focus {
outline: none !important;
}
.container {
padding: 5px 0 0 10px;
box-sizing: border-box;
user-select: none;
}
.container.image {
padding: 0;
display: flex;
box-sizing: border-box;
}
.container.image img {
padding: 0;
background-position: 0 0, 8px 8px;
background-size: 16px 16px;
}
.container.image img {
background-image:
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)),
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230));
}
.vscode-dark.container.image img {
background-image:
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)),
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
}
.container img.pixelated {
image-rendering: pixelated;
}
.container img.scale-to-fit {
max-width: calc(100% - 20px);
max-height: calc(100% - 20px);
object-fit: contain;
}
.container img {
margin: auto;
}
.container.zoom-in {
cursor: zoom-in;
}
.container.zoom-out {
cursor: zoom-out;
}
.container .embedded-link,
.container .embedded-link:hover {
cursor: pointer;
text-decoration: underline;
margin-left: 5px;
}

View File

@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// @ts-check
"use strict";
(function () {
/**
* @param {number} value
* @param {number} min
* @param {number} max
* @return {number}
*/
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function getSettings() {
const element = document.getElementById('image-preview-settings');
if (element) {
const data = element.getAttribute('data-settings');
if (data) {
return JSON.parse(data);
}
}
throw new Error(`Could not load settings`);
}
/**
* Enable image-rendering: pixelated for images scaled by more than this.
*/
const PIXELATION_THRESHOLD = 3;
const SCALE_PINCH_FACTOR = 0.075;
const MAX_SCALE = 20;
const MIN_SCALE = 0.1;
const zoomLevels = [
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
0.9,
1,
1.5,
2,
3,
5,
7,
10,
15,
20
];
const isMac = getSettings().isMac;
const vscode = acquireVsCodeApi();
const initialState = vscode.getState() || { scale: 'fit', offsetX: 0, offsetY: 0 };
// State
let scale = initialState.scale;
let ctrlPressed = false;
let altPressed = false;
// Elements
const container = /** @type {HTMLElement} */(document.querySelector('body'));
const image = document.querySelector('img');
function updateScale(newScale) {
if (!image || !image.parentElement) {
return;
}
if (newScale === 'fit') {
scale = 'fit';
image.classList.add('scale-to-fit');
image.classList.remove('pixelated');
image.style.minWidth = 'auto';
image.style.width = 'auto';
vscode.setState(undefined);
} else {
const oldWidth = image.width;
const oldHeight = image.height;
scale = clamp(newScale, MIN_SCALE, MAX_SCALE);
if (scale >= PIXELATION_THRESHOLD) {
image.classList.add('pixelated');
} else {
image.classList.remove('pixelated');
}
const { scrollTop, scrollLeft } = image.parentElement;
const dx = (scrollLeft + image.parentElement.clientWidth / 2) / image.parentElement.scrollWidth;
const dy = (scrollTop + image.parentElement.clientHeight / 2) / image.parentElement.scrollHeight;
image.classList.remove('scale-to-fit');
image.style.minWidth = `${(image.naturalWidth * scale)}px`;
image.style.width = `${(image.naturalWidth * scale)}px`;
const newWidth = image.width;
const scaleFactor = (newWidth - oldWidth) / oldWidth;
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
const newScrollTop = ((oldHeight * scaleFactor * dy) + scrollTop);
// scrollbar.setScrollPosition({
// scrollLeft: newScrollLeft,
// scrollTop: newScrollTop,
// });
vscode.setState({ scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop });
}
vscode.postMessage({
type: 'zoom',
value: scale
});
}
function firstZoom() {
if (!image) {
return;
}
scale = image.clientWidth / image.naturalWidth;
updateScale(scale);
}
window.addEventListener('keydown', (/** @type {KeyboardEvent} */ e) => {
if (!image) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (isMac ? altPressed : ctrlPressed) {
container.classList.remove('zoom-in');
container.classList.add('zoom-out');
}
});
window.addEventListener('keyup', (/** @type {KeyboardEvent} */ e) => {
if (!image) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (!(isMac ? altPressed : ctrlPressed)) {
container.classList.remove('zoom-out');
container.classList.add('zoom-in');
}
});
container.addEventListener('click', (/** @type {MouseEvent} */ e) => {
if (!image) {
return;
}
if (e.button !== 0) {
return;
}
// left click
if (scale === 'fit') {
firstZoom();
}
if (!(isMac ? altPressed : ctrlPressed)) { // zoom in
let i = 0;
for (; i < zoomLevels.length; ++i) {
if (zoomLevels[i] > scale) {
break;
}
}
updateScale(zoomLevels[i] || MAX_SCALE);
} else {
let i = zoomLevels.length - 1;
for (; i >= 0; --i) {
if (zoomLevels[i] < scale) {
break;
}
}
updateScale(zoomLevels[i] || MIN_SCALE);
}
});
container.addEventListener('wheel', (/** @type {WheelEvent} */ e) => {
if (!image) {
return;
}
const isScrollWheelKeyPressed = isMac ? altPressed : ctrlPressed;
if (!isScrollWheelKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
return;
}
e.preventDefault();
e.stopPropagation();
if (scale === 'fit') {
firstZoom();
}
let delta = e.deltaY > 0 ? 1 : -1;
updateScale(scale * (1 - delta * SCALE_PINCH_FACTOR));
});
window.addEventListener('scroll', () => {
if (!image || !image.parentElement || scale === 'fit') {
return;
}
const entry = vscode.getState();
if (entry) {
vscode.setState({ scale: entry.scale, offsetX: window.scrollX, offsetY: window.scrollY });
}
});
container.classList.add('image');
container.classList.add('zoom-in');
image.classList.add('scale-to-fit');
image.style.visibility = 'hidden';
image.addEventListener('load', () => {
if (!image) {
return;
}
vscode.postMessage({
type: 'size',
value: `${image.naturalWidth}x${image.naturalHeight}`,
});
image.style.visibility = 'visible';
updateScale(scale);
if (initialState.scale !== 'fit') {
window.scrollTo(initialState.offsetX, initialState.offsetY);
}
});
window.addEventListener('message', e => {
switch (e.data.type) {
case 'setScale':
updateScale(e.data.scale);
break;
}
});
}());

View File

@@ -0,0 +1,43 @@
{
"name": "image-preview",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.0.0",
"publisher": "vscode",
"enableProposedApi": true,
"license": "MIT",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.39.0"
},
"main": "./out/extension",
"categories": [
"Other"
],
"activationEvents": [
"onWebviewEditor:imagePreview.previewEditor"
],
"contributes": {
"webviewEditors": [
{
"viewType": "imagePreview.previewEditor",
"displayName": "%webviewEditors.displayName%",
"selector": [
{
"filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,tif,tiff,webp}"
}
]
}
]
},
"scripts": {
"compile": "gulp compile-extension:image-preview",
"watch": "npm run build-preview && gulp watch-extension:image-preview",
"vscode:prepublish": "npm run build-ext",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:image-preview ./tsconfig.json"
},
"dependencies": {
"vscode-extension-telemetry": "0.1.1",
"vscode-nls": "^4.0.0"
}
}

View File

@@ -0,0 +1,5 @@
{
"displayName": "Image Preview",
"description": "Previews images.",
"webviewEditors.displayName": "Image Preview"
}

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function disposeAll(disposables: vscode.Disposable[]) {
while (disposables.length) {
const item = disposables.pop();
if (item) {
item.dispose();
}
}
}
export abstract class Disposable {
private _isDisposed = false;
protected _disposables: vscode.Disposable[] = [];
public dispose(): any {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
disposeAll(this._disposables);
}
protected _register<T extends vscode.Disposable>(value: T): T {
if (this._isDisposed) {
value.dispose();
} else {
this._disposables.push(value);
}
return value;
}
protected get isDisposed() {
return this._isDisposed;
}
}

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Preview } from './preview';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
export function activate(context: vscode.ExtensionContext) {
const extensionRoot = vscode.Uri.file(context.extensionPath);
const sizeStatusBarEntry = new SizeStatusBarEntry();
context.subscriptions.push(sizeStatusBarEntry);
const zoomStatusBarEntry = new ZoomStatusBarEntry();
context.subscriptions.push(zoomStatusBarEntry);
context.subscriptions.push(vscode.window.registerWebviewEditorProvider(
Preview.viewType,
{
async resolveWebviewEditor(resource: vscode.Uri, editor: vscode.WebviewEditor): Promise<void> {
// tslint:disable-next-line: no-unused-expression
new Preview(extensionRoot, resource, editor, sizeStatusBarEntry, zoomStatusBarEntry);
}
}));
}

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
import { Disposable } from './dispose';
export class Preview extends Disposable {
public static readonly viewType = 'imagePreview.previewEditor';
private _active = true;
constructor(
private readonly extensionRoot: vscode.Uri,
resource: vscode.Uri,
private readonly webviewEditor: vscode.WebviewEditor,
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
) {
super();
const resourceRoot = resource.with({
path: resource.path.replace(/\/[^\/]+?\.\w+$/, '/'),
});
webviewEditor.webview.options = {
enableScripts: true,
localResourceRoots: [
resourceRoot,
extensionRoot,
]
};
webviewEditor.webview.html = this.getWebiewContents(webviewEditor, resource);
this._register(webviewEditor.webview.onDidReceiveMessage(message => {
switch (message.type) {
case 'size':
{
this.sizeStatusBarEntry.update(message.value);
break;
}
case 'zoom':
{
this.zoomStatusBarEntry.update(message.value);
break;
}
}
}));
this._register(zoomStatusBarEntry.onDidChangeScale(e => {
this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
}));
this._register(webviewEditor.onDidChangeViewState(() => {
this.update();
}));
this._register(webviewEditor.onDidDispose(() => {
if (this._active) {
this.sizeStatusBarEntry.hide();
this.zoomStatusBarEntry.hide();
}
}));
this.update();
}
private update() {
this._active = this.webviewEditor.active;
if (this._active) {
this.sizeStatusBarEntry.show();
this.zoomStatusBarEntry.show();
} else {
this.sizeStatusBarEntry.hide();
this.zoomStatusBarEntry.hide();
}
}
private getWebiewContents(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri): string {
const settings = {
isMac: process.platform === 'darwin'
};
return /* html */`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Image Preview</title>
<link rel="stylesheet" class="code-user-style" href="${escapeAttribute(this.extensionResource('/media/main.css'))}" type="text/css" media="screen">
<meta id="image-preview-settings" data-settings="${escapeAttribute(JSON.stringify(settings))}">
</head>
<body class="container image scale-to-fit">
<img src="${escapeAttribute(webviewEditor.webview.asWebviewUri(resource))}">
<script src="${escapeAttribute(this.extensionResource('/media/main.js'))}"></script>
</body>
</html>`;
}
private extensionResource(path: string) {
return this.webviewEditor.webview.asWebviewUri(this.extensionRoot.with({
path: this.extensionRoot.path + path
}));
}
}
function escapeAttribute(value: string | vscode.Uri): string {
return value.toString().replace(/"/g, '&quot;');
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Disposable } from './dispose';
export class SizeStatusBarEntry extends Disposable {
private readonly _entry: vscode.StatusBarItem;
constructor() {
super();
this._entry = this._register(vscode.window.createStatusBarItem({
id: 'imagePreview.size',
name: 'Image Size',
alignment: vscode.StatusBarAlignment.Right,
priority: 101 /* to the left of editor status (100) */,
}));
}
public show() {
this._entry.show();
}
public hide() {
this._entry.hide();
}
public update(text: string) {
this._entry.text = text;
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Disposable } from './dispose';
const localize = nls.loadMessageBundle();
const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel';
type Scale = number | 'fit';
export class ZoomStatusBarEntry extends Disposable {
private readonly _entry: vscode.StatusBarItem;
private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>());
public readonly onDidChangeScale = this._onDidChangeScale.event;
constructor() {
super();
this._entry = this._register(vscode.window.createStatusBarItem({
id: 'imagePreview.zoom',
name: 'Image Zoom',
alignment: vscode.StatusBarAlignment.Right,
priority: 102 /* to the left of editor size entry (101) */,
}));
this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => {
type MyPickItem = vscode.QuickPickItem & { scale: Scale };
const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
const options = scales.map((scale): MyPickItem => ({
label: this.zoomLabel(scale),
scale
}));
const pick = await vscode.window.showQuickPick(options, {
placeHolder: localize('zoomStatusBar.placeholder', "Select zoom level")
});
if (pick) {
this._onDidChangeScale.fire({ scale: pick.scale });
}
}));
this._entry.command = selectZoomLevelCommandId;
}
public show() {
this._entry.show();
}
public hide() {
this._entry.hide();
}
public update(scale: Scale) {
this._entry.text = this.zoomLabel(scale);
}
private zoomLabel(scale: Scale): string {
return scale === 'fit'
? localize('zoomStatusBar.wholeImageLabel', "Whole Image")
: `${Math.round(scale * 100)}%`;
}
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true
},
"include": [
"src/**/*"
]
}

View File

@@ -0,0 +1,46 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg==
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
zone.js "0.7.6"
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
semver@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
vscode-extension-telemetry@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b"
integrity sha512-TkKKG/B/J94DP5qf6xWB4YaqlhWDg6zbbqVx7Bz//stLQNnfE9XS1xm3f6fl24c5+bnEK0/wHgMgZYKIKxPeUA==
dependencies:
applicationinsights "1.0.8"
vscode-nls@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=