195 lines
5.9 KiB
TypeScript
195 lines
5.9 KiB
TypeScript
/**
|
||
* @license
|
||
* Copyright (C) 2016 The Android Open Source Project
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
|
||
import {DiffInfo, DiffPreferencesInfo, ImageInfo} from '../../../types/common';
|
||
import {GrEndpointParam} from '../../plugins/gr-endpoint-param/gr-endpoint-param';
|
||
|
||
// MIME types for images we allow showing. Do not include SVG, it can contain
|
||
// arbitrary JavaScript.
|
||
const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|x-icon|jpeg|jpg|png|tiff|webp)$/;
|
||
|
||
export class GrDiffBuilderImage extends GrDiffBuilderSideBySide {
|
||
constructor(
|
||
diff: DiffInfo,
|
||
prefs: DiffPreferencesInfo,
|
||
outputEl: HTMLElement,
|
||
private readonly _baseImage: ImageInfo | null,
|
||
private readonly _revisionImage: ImageInfo | null
|
||
) {
|
||
super(diff, prefs, outputEl, []);
|
||
}
|
||
|
||
public renderDiff() {
|
||
const section = this._createElement('tbody', 'image-diff');
|
||
|
||
this._emitImagePair(section);
|
||
this._emitImageLabels(section);
|
||
|
||
this._outputEl.appendChild(section);
|
||
this._outputEl.appendChild(this._createEndpoint());
|
||
}
|
||
|
||
private _createEndpoint() {
|
||
const tbody = this._createElement('tbody');
|
||
const tr = this._createElement('tr');
|
||
const td = this._createElement('td');
|
||
|
||
// TODO(kaspern): Support blame for image diffs and remove the hardcoded 4
|
||
// column limit.
|
||
td.setAttribute('colspan', '4');
|
||
const endpointDomApi = this._createElement('gr-endpoint-decorator');
|
||
endpointDomApi.setAttribute('name', 'image-diff');
|
||
endpointDomApi.appendChild(
|
||
this._createEndpointParam('baseImage', this._baseImage)
|
||
);
|
||
endpointDomApi.appendChild(
|
||
this._createEndpointParam('revisionImage', this._revisionImage)
|
||
);
|
||
td.appendChild(endpointDomApi);
|
||
tr.appendChild(td);
|
||
tbody.appendChild(tr);
|
||
return tbody;
|
||
}
|
||
|
||
private _createEndpointParam(name: string, value: ImageInfo | null) {
|
||
const endpointParam = this._createElement(
|
||
'gr-endpoint-param'
|
||
) as GrEndpointParam;
|
||
endpointParam.name = name;
|
||
endpointParam.value = value;
|
||
return endpointParam;
|
||
}
|
||
|
||
private _emitImagePair(section: HTMLElement) {
|
||
const tr = this._createElement('tr');
|
||
|
||
tr.appendChild(this._createElement('td', 'left lineNum blank'));
|
||
tr.appendChild(this._createImageCell(this._baseImage, 'left', section));
|
||
|
||
tr.appendChild(this._createElement('td', 'right lineNum blank'));
|
||
tr.appendChild(
|
||
this._createImageCell(this._revisionImage, 'right', section)
|
||
);
|
||
|
||
section.appendChild(tr);
|
||
}
|
||
|
||
private _createImageCell(
|
||
image: ImageInfo | null,
|
||
className: string,
|
||
section: HTMLElement
|
||
) {
|
||
const td = this._createElement('td', className);
|
||
if (image && IMAGE_MIME_PATTERN.test(image.type)) {
|
||
const imageEl = this._createElement('img') as HTMLImageElement;
|
||
imageEl.onload = () => {
|
||
image._height = imageEl.naturalHeight;
|
||
image._width = imageEl.naturalWidth;
|
||
this._updateImageLabel(section, className, image);
|
||
};
|
||
imageEl.setAttribute('src', `data:${image.type};base64, ${image.body}`);
|
||
imageEl.addEventListener('error', (e: Event) => {
|
||
imageEl.remove();
|
||
td.textContent = '[Image failed to load] ' + e.type;
|
||
});
|
||
td.appendChild(imageEl);
|
||
}
|
||
return td;
|
||
}
|
||
|
||
private _updateImageLabel(
|
||
section: HTMLElement,
|
||
className: string,
|
||
image: ImageInfo
|
||
) {
|
||
const label = section.querySelector(
|
||
'.' + className + ' span.label'
|
||
) as HTMLElement;
|
||
this._setLabelText(label, image);
|
||
}
|
||
|
||
private _setLabelText(label: HTMLElement, image: ImageInfo | null) {
|
||
label.textContent = _getImageLabel(image);
|
||
}
|
||
|
||
private _emitImageLabels(section: HTMLElement) {
|
||
const tr = this._createElement('tr');
|
||
|
||
let addNamesInLabel = false;
|
||
|
||
if (
|
||
this._baseImage &&
|
||
this._revisionImage &&
|
||
this._baseImage._name !== this._revisionImage._name
|
||
) {
|
||
addNamesInLabel = true;
|
||
}
|
||
|
||
tr.appendChild(this._createElement('td', 'left lineNum blank'));
|
||
let td = this._createElement('td', 'left');
|
||
let label = this._createElement('label');
|
||
let nameSpan;
|
||
let labelSpan = this._createElement('span', 'label');
|
||
|
||
if (addNamesInLabel) {
|
||
nameSpan = this._createElement('span', 'name');
|
||
nameSpan.textContent = this._baseImage?._name ?? '';
|
||
label.appendChild(nameSpan);
|
||
label.appendChild(this._createElement('br'));
|
||
}
|
||
|
||
this._setLabelText(labelSpan, this._baseImage);
|
||
|
||
label.appendChild(labelSpan);
|
||
td.appendChild(label);
|
||
tr.appendChild(td);
|
||
|
||
tr.appendChild(this._createElement('td', 'right lineNum blank'));
|
||
td = this._createElement('td', 'right');
|
||
label = this._createElement('label');
|
||
labelSpan = this._createElement('span', 'label');
|
||
|
||
if (addNamesInLabel) {
|
||
nameSpan = this._createElement('span', 'name');
|
||
nameSpan.textContent = this._revisionImage?._name ?? '';
|
||
label.appendChild(nameSpan);
|
||
label.appendChild(this._createElement('br'));
|
||
}
|
||
|
||
this._setLabelText(labelSpan, this._revisionImage);
|
||
|
||
label.appendChild(labelSpan);
|
||
td.appendChild(label);
|
||
tr.appendChild(td);
|
||
|
||
section.appendChild(tr);
|
||
}
|
||
}
|
||
|
||
function _getImageLabel(image: ImageInfo | null) {
|
||
if (image) {
|
||
const type = image.type ?? image._expectedType;
|
||
if (image._width && image._height) {
|
||
return `${image._width}×${image._height} ${type}`;
|
||
} else {
|
||
return type;
|
||
}
|
||
}
|
||
return 'No image';
|
||
}
|