static create()

in src/vs/workbench/browser/parts/editor/resourceViewer.ts [349:568]


	static create(
		container: HTMLElement,
		descriptor: IResourceDescriptor,
		fileService: IFileService,
		scrollbar: DomScrollableElement,
		delegate: ResourceViewerDelegate,
		@IInstantiationService instantiationService: IInstantiationService,
	) {
		const disposables = new DisposableStore();

		const zoomStatusbarItem = disposables.add(instantiationService.createInstance(ZoomStatusbarItem,
			newScale => updateScale(newScale)));

		const context: ResourceViewerContext = {
			layout(dimension: DOM.Dimension) { },
			dispose: () => disposables.dispose()
		};

		const cacheKey = `${descriptor.resource.toString()}:${descriptor.etag}`;

		let ctrlPressed = false;
		let altPressed = false;

		const initialState: ImageState = InlineImageView.imageStateCache.get(cacheKey) || { scale: 'fit', offsetX: 0, offsetY: 0 };
		let scale = initialState.scale;
		let image: HTMLImageElement | null = null;

		function updateScale(newScale: Scale) {
			if (!image || !image.parentElement) {
				return;
			}

			if (newScale === 'fit') {
				scale = 'fit';
				DOM.addClass(image, 'scale-to-fit');
				DOM.removeClass(image, 'pixelated');
				image.style.minWidth = 'auto';
				image.style.width = 'auto';
				InlineImageView.imageStateCache.delete(cacheKey);
			} else {
				const oldWidth = image.width;
				const oldHeight = image.height;

				scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
				if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
					DOM.addClass(image, 'pixelated');
				} else {
					DOM.removeClass(image, '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;

				DOM.removeClass(image, '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,
				});

				InlineImageView.imageStateCache.set(cacheKey, { scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop });
			}

			zoomStatusbarItem.updateStatusbar(scale);
			scrollbar.scanDomNode();
		}

		function firstZoom() {
			if (!image) {
				return;
			}

			scale = image.clientWidth / image.naturalWidth;
			updateScale(scale);
		}

		disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
			if (!image) {
				return;
			}
			ctrlPressed = e.ctrlKey;
			altPressed = e.altKey;

			if (platform.isMacintosh ? altPressed : ctrlPressed) {
				DOM.removeClass(container, 'zoom-in');
				DOM.addClass(container, 'zoom-out');
			}
		}));

		disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
			if (!image) {
				return;
			}

			ctrlPressed = e.ctrlKey;
			altPressed = e.altKey;

			if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
				DOM.removeClass(container, 'zoom-out');
				DOM.addClass(container, 'zoom-in');
			}
		}));

		disposables.add(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => {
			if (!image) {
				return;
			}

			if (e.button !== 0) {
				return;
			}

			// left click
			if (scale === 'fit') {
				firstZoom();
			}

			if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
				let i = 0;
				for (; i < InlineImageView.zoomLevels.length; ++i) {
					if (InlineImageView.zoomLevels[i] > scale) {
						break;
					}
				}
				updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
			} else {
				let i = InlineImageView.zoomLevels.length - 1;
				for (; i >= 0; --i) {
					if (InlineImageView.zoomLevels[i] < scale) {
						break;
					}
				}
				updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
			}
		}));

		disposables.add(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => {
			if (!image) {
				return;
			}

			const isScrollWheelKeyPressed = platform.isMacintosh ? 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 as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
		}));

		disposables.add(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => {
			if (!image || !image.parentElement || scale === 'fit') {
				return;
			}

			const entry = InlineImageView.imageStateCache.get(cacheKey);
			if (entry) {
				const { scrollTop, scrollLeft } = image.parentElement;
				InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
			}
		}));

		DOM.clearNode(container);
		DOM.addClasses(container, 'image', 'zoom-in');

		image = DOM.append(container, DOM.$<HTMLImageElement>('img.scale-to-fit'));
		image.style.visibility = 'hidden';

		disposables.add(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => {
			if (!image) {
				return;
			}
			if (typeof descriptor.size === 'number') {
				delegate.metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', image.naturalWidth, image.naturalHeight, BinarySize.formatSize(descriptor.size)));
			} else {
				delegate.metadataClb(nls.localize('imgMetaNoSize', '{0}x{1}', image.naturalWidth, image.naturalHeight));
			}

			scrollbar.scanDomNode();
			image.style.visibility = 'visible';
			updateScale(scale);
			if (initialState.scale !== 'fit') {
				scrollbar.setScrollPosition({
					scrollLeft: initialState.offsetX,
					scrollTop: initialState.offsetY,
				});
			}
		}));

		InlineImageView.imageSrc(descriptor, fileService).then(src => {
			const img = container.querySelector('img');
			if (img) {
				if (typeof src === 'string') {
					img.src = src;
				} else {
					const url = URL.createObjectURL(src);
					disposables.add(toDisposable(() => URL.revokeObjectURL(url)));
					img.src = url;
				}
			}
		});

		return context;
	}