Skip to content
Snippets Groups Projects
Commit b54203f0 authored by Felipe Artur's avatar Felipe Artur Committed by Jacob Schatz
Browse files

Commenting on image diffs

parent b4f9dc48
No related branches found
No related tags found
No related merge requests found
Showing
with 563 additions and 41 deletions
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */
window.Commit = (function() {
function Commit() {
$('.files .diff-file').each(function() {
return new CommitFile(this);
});
}
return Commit;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */
/* global ImageFile */
(function() {
this.CommitFile = (function() {
function CommitFile(file) {
if ($('.image', file).length) {
new gl.ImageFile(file);
}
}
return CommitFile;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
import 'vendor/jquery.waitforimages';
(function() {
gl.ImageFile = (function() {
var prepareFrames;
Loading
Loading
@@ -17,15 +19,10 @@
 
// Load two-up view after images are loaded
// so that we can display the correct width and height information
const images = $('.two-up.view img', _this.file);
let loadedCount = 0;
images.on('load', () => {
loadedCount += 1;
const $images = $('.two-up.view img', _this.file);
 
if (loadedCount === images.length) {
_this.initView('two-up');
}
$images.waitForImages(function() {
_this.initView('two-up');
});
});
};
Loading
Loading
Loading
Loading
@@ -3,6 +3,7 @@
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index';
 
const UNFOLD_COUNT = 20;
let isBound = false;
Loading
Loading
@@ -20,7 +21,9 @@ class Diff {
const tab = document.getElementById('diffs');
if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile);
 
$diffFile.each((index, file) => new gl.ImageFile(file));
const firstFile = $('.files').first().get(0);
const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
$diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote));
 
if (!isBound) {
$(document)
Loading
Loading
Loading
Loading
@@ -171,7 +171,14 @@ const JumpToDiscussion = Vue.extend({
// When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show();
 
$target = $target.closest("tr.notes_holder");
const $notesHolder = $target.closest("tr.notes_holder");
// Image diff discussions does not use notes_holder
// so we should keep original $target value in those cases
if ($notesHolder.length > 0) {
$target = $notesHolder;
}
$target.show();
 
// If we are on the diffs tab, we don't scroll to the discussion itself, but to
Loading
Loading
Loading
Loading
@@ -7,7 +7,6 @@
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
/* global Commit */
/* global CommitsList */
/* global NewBranchForm */
/* global NotificationsForm */
Loading
Loading
@@ -316,7 +315,6 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new gl.Activities();
break;
case 'projects:commit:show':
new Commit();
new gl.Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
Loading
Loading
export function createImageBadge(noteId, { x, y }, classNames = []) {
const buttonEl = document.createElement('button');
const classList = classNames.concat(['js-image-badge']);
classList.forEach(className => buttonEl.classList.add(className));
buttonEl.setAttribute('type', 'button');
buttonEl.setAttribute('disabled', true);
buttonEl.dataset.noteId = noteId;
buttonEl.style.left = `${x}px`;
buttonEl.style.top = `${y}px`;
return buttonEl;
}
export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['badge']);
buttonEl.innerText = badgeText;
containerEl.appendChild(buttonEl);
}
export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']);
const iconEl = document.createElement('i');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl);
}
export function addAvatarBadge(el, event) {
const { noteId, badgeNumber } = event.detail;
// Add badge to new comment
const avatarBadgeEl = el.querySelector(`#${noteId} .badge`);
avatarBadgeEl.innerText = badgeNumber;
avatarBadgeEl.classList.remove('hidden');
}
export function addCommentIndicator(containerEl, { x, y }) {
const buttonEl = document.createElement('button');
buttonEl.classList.add('btn-transparent');
buttonEl.classList.add('comment-indicator');
buttonEl.setAttribute('type', 'button');
buttonEl.style.left = `${x}px`;
buttonEl.style.top = `${y}px`;
buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
containerEl.appendChild(buttonEl);
}
export function removeCommentIndicator(imageFrameEl) {
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
const imageEl = imageFrameEl.querySelector('img');
const willRemove = !!commentIndicatorEl;
let meta = {};
if (willRemove) {
meta = {
x: parseInt(commentIndicatorEl.style.left, 10),
y: parseInt(commentIndicatorEl.style.top, 10),
image: {
width: imageEl.width,
height: imageEl.height,
},
};
commentIndicatorEl.remove();
}
return Object.assign({}, meta, {
removed: willRemove,
});
}
export function showCommentIndicator(imageFrameEl, coordinate) {
const { x, y } = coordinate;
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
if (commentIndicatorEl) {
commentIndicatorEl.style.left = `${x}px`;
commentIndicatorEl.style.top = `${y}px`;
} else {
addCommentIndicator(imageFrameEl, coordinate);
}
}
export function commentIndicatorOnClick(event) {
// Prevent from triggering onAddImageDiffNote in notes.js
event.stopPropagation();
const buttonEl = event.currentTarget;
const diffViewerEl = buttonEl.closest('.diff-viewer');
const textareaEl = diffViewerEl.querySelector('.note-container .note-textarea');
textareaEl.focus();
}
export function setPositionDataAttribute(el, options) {
// Update position data attribute so that the
// new comment form can use this data for ajax request
const { x, y, width, height } = options;
const position = el.dataset.position;
const positionObject = Object.assign({}, JSON.parse(position), {
x,
y,
width,
height,
});
el.setAttribute('data-position', JSON.stringify(positionObject));
}
export function updateDiscussionAvatarBadgeNumber(discussionEl, newBadgeNumber) {
const avatarBadgeEl = discussionEl.querySelector('.image-diff-avatar-link .badge');
avatarBadgeEl.innerText = newBadgeNumber;
}
export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) {
const discussionBadgeEl = discussionEl.querySelector('.badge');
discussionBadgeEl.innerText = newBadgeNumber;
}
export function toggleCollapsed(event) {
const toggleButtonEl = event.currentTarget;
const discussionNotesEl = toggleButtonEl.closest('.discussion-notes');
const formEl = discussionNotesEl.querySelector('.discussion-form');
const isCollapsed = discussionNotesEl.classList.contains('collapsed');
if (isCollapsed) {
discussionNotesEl.classList.remove('collapsed');
} else {
discussionNotesEl.classList.add('collapsed');
}
// Override the inline display style set in notes.js
if (formEl && !isCollapsed) {
formEl.style.display = 'none';
} else if (formEl && isCollapsed) {
formEl.style.display = 'block';
}
}
import * as badgeHelper from './badge_helper';
import * as commentIndicatorHelper from './comment_indicator_helper';
import * as domHelper from './dom_helper';
import * as utilsHelper from './utils_helper';
export default {
addCommentIndicator: commentIndicatorHelper.addCommentIndicator,
removeCommentIndicator: commentIndicatorHelper.removeCommentIndicator,
showCommentIndicator: commentIndicatorHelper.showCommentIndicator,
commentIndicatorOnClick: commentIndicatorHelper.commentIndicatorOnClick,
addImageBadge: badgeHelper.addImageBadge,
addImageCommentBadge: badgeHelper.addImageCommentBadge,
addAvatarBadge: badgeHelper.addAvatarBadge,
setPositionDataAttribute: domHelper.setPositionDataAttribute,
updateDiscussionAvatarBadgeNumber: domHelper.updateDiscussionAvatarBadgeNumber,
updateDiscussionBadgeNumber: domHelper.updateDiscussionBadgeNumber,
toggleCollapsed: domHelper.toggleCollapsed,
resizeCoordinatesToImageElement: utilsHelper.resizeCoordinatesToImageElement,
generateBadgeFromDiscussionDOM: utilsHelper.generateBadgeFromDiscussionDOM,
getTargetSelection: utilsHelper.getTargetSelection,
initImageDiff: utilsHelper.initImageDiff,
};
import ImageBadge from '../image_badge';
import ImageDiff from '../image_diff';
import ReplacedImageDiff from '../replaced_image_diff';
import '../../commit/image_file';
export function resizeCoordinatesToImageElement(imageEl, meta) {
const { x, y, width, height } = meta;
const imageWidth = imageEl.width;
const imageHeight = imageEl.height;
const widthRatio = imageWidth / width;
const heightRatio = imageHeight / height;
return {
x: Math.round(x * widthRatio),
y: Math.round(y * heightRatio),
width: imageWidth,
height: imageHeight,
};
}
export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) {
const position = JSON.parse(discussionEl.dataset.position);
const firstNoteEl = discussionEl.querySelector('.note');
const badge = new ImageBadge({
actual: position,
imageEl: imageFrameEl.querySelector('img'),
noteId: firstNoteEl.id,
discussionId: discussionEl.dataset.discussionId,
});
return badge;
}
export function getTargetSelection(event) {
const containerEl = event.currentTarget;
const imageEl = containerEl.querySelector('img');
const x = event.offsetX;
const y = event.offsetY;
const width = imageEl.width;
const height = imageEl.height;
const actualWidth = imageEl.naturalWidth;
const actualHeight = imageEl.naturalHeight;
const widthRatio = actualWidth / width;
const heightRatio = actualHeight / height;
// Browser will include the frame as a clickable target,
// which would result in potential 1px out of bounds value
// This bound the coordinates to inside the frame
const normalizedX = Math.max(0, x) && Math.min(x, width);
const normalizedY = Math.max(0, y) && Math.min(y, height);
return {
browser: {
x: normalizedX,
y: normalizedY,
width,
height,
},
actual: {
// Round x, y so that we don't need to deal with decimals
x: Math.round(normalizedX * widthRatio),
y: Math.round(normalizedY * heightRatio),
width: actualWidth,
height: actualHeight,
},
};
}
export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) {
const options = {
canCreateNote,
renderCommentBadge,
};
let diff;
// ImageFile needs to be invoked before initImageDiff so that badges
// can mount to the correct location
new gl.ImageFile(fileEl); // eslint-disable-line no-new
if (fileEl.querySelector('.diff-file .js-single-image')) {
diff = new ImageDiff(fileEl, options);
diff.init();
} else if (fileEl.querySelector('.diff-file .js-replaced-image')) {
diff = new ReplacedImageDiff(fileEl, options);
diff.init();
}
return diff;
}
import imageDiffHelper from './helpers/index';
const defaultMeta = {
x: 0,
y: 0,
width: 0,
height: 0,
};
export default class ImageBadge {
constructor(options) {
const { noteId, discussionId } = options;
this.actual = options.actual || defaultMeta;
this.browser = options.browser || defaultMeta;
this.noteId = noteId;
this.discussionId = discussionId;
if (options.imageEl && !options.browser) {
this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual);
}
}
}
import imageDiffHelper from './helpers/index';
import ImageBadge from './image_badge';
import { isImageLoaded } from '../lib/utils/image_utility';
export default class ImageDiff {
constructor(el, options) {
this.el = el;
this.canCreateNote = !!(options && options.canCreateNote);
this.renderCommentBadge = !!(options && options.renderCommentBadge);
this.$noteContainer = $('.note-container', this.el);
this.imageBadges = [];
}
init() {
this.imageFrameEl = this.el.querySelector('.diff-file .js-image-frame');
this.imageEl = this.imageFrameEl.querySelector('img');
this.bindEvents();
}
bindEvents() {
this.imageClickedWrapper = this.imageClicked.bind(this);
this.imageBlurredWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl);
this.addBadgeWrapper = this.addBadge.bind(this);
this.removeBadgeWrapper = this.removeBadge.bind(this);
this.renderBadgesWrapper = this.renderBadges.bind(this);
// Render badges
if (isImageLoaded(this.imageEl)) {
this.renderBadges();
} else {
this.imageEl.addEventListener('load', this.renderBadgesWrapper);
}
// jquery makes the event delegation here much simpler
this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed);
$(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick);
if (this.canCreateNote) {
this.el.addEventListener('click.imageDiff', this.imageClickedWrapper);
this.el.addEventListener('blur.imageDiff', this.imageBlurredWrapper);
this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper);
this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper);
}
}
imageClicked(event) {
const customEvent = event.detail;
const selection = imageDiffHelper.getTargetSelection(customEvent);
const el = customEvent.currentTarget;
imageDiffHelper.setPositionDataAttribute(el, selection.actual);
imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser);
}
renderBadges() {
const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes');
[...discussionsEls].forEach(this.renderBadge.bind(this));
}
renderBadge(discussionEl, index) {
const imageBadge = imageDiffHelper
.generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
this.imageBadges.push(imageBadge);
const options = {
coordinate: imageBadge.browser,
noteId: imageBadge.noteId,
};
if (this.renderCommentBadge) {
imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
} else {
const numberBadgeOptions = Object.assign({}, options, {
badgeText: index + 1,
});
imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
}
}
addBadge(event) {
const { x, y, width, height, noteId, discussionId } = event.detail;
const badgeText = this.imageBadges.length + 1;
const imageBadge = new ImageBadge({
actual: {
x,
y,
width,
height,
},
imageEl: this.imageFrameEl.querySelector('img'),
noteId,
discussionId,
});
this.imageBadges.push(imageBadge);
imageDiffHelper.addImageBadge(this.imageFrameEl, {
coordinate: imageBadge.browser,
badgeText,
noteId,
});
imageDiffHelper.addAvatarBadge(this.el, {
detail: {
noteId,
badgeNumber: badgeText,
},
});
const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText);
}
removeBadge(event) {
const { badgeNumber } = event.detail;
const indexToRemove = badgeNumber - 1;
const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge');
if (this.imageBadges.length !== badgeNumber) {
// Cascade badges count numbers for (avatar badges + image badges)
this.imageBadges.forEach((badge, index) => {
if (index > indexToRemove) {
const { discussionId } = badge;
const updatedBadgeNumber = index;
const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
imageBadgeEls[index].innerText = updatedBadgeNumber;
imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber);
imageDiffHelper.updateDiscussionAvatarBadgeNumber(discussionEl, updatedBadgeNumber);
}
});
}
this.imageBadges.splice(indexToRemove, 1);
const imageBadgeEl = imageBadgeEls[indexToRemove];
imageBadgeEl.remove();
}
}
import imageDiffHelper from './helpers/index';
export default () => {
// Always pass can-create-note as false because a user
// cannot place new badge markers on discussion tab
const canCreateNote = false;
const renderCommentBadge = true;
const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file');
[...diffFileEls].forEach(diffFileEl =>
imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge));
};
import imageDiffHelper from './helpers/index';
import { viewTypes, isValidViewType } from './view_types';
import ImageDiff from './image_diff';
export default class ReplacedImageDiff extends ImageDiff {
init(defaultViewType = viewTypes.TWO_UP) {
this.imageFrameEls = {
[viewTypes.TWO_UP]: this.el.querySelector('.two-up .js-image-frame'),
[viewTypes.SWIPE]: this.el.querySelector('.swipe .js-image-frame'),
[viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .js-image-frame'),
};
const viewModesEl = this.el.querySelector('.view-modes-menu');
this.viewModesEls = {
[viewTypes.TWO_UP]: viewModesEl.querySelector('.two-up'),
[viewTypes.SWIPE]: viewModesEl.querySelector('.swipe'),
[viewTypes.ONION_SKIN]: viewModesEl.querySelector('.onion-skin'),
};
this.currentView = defaultViewType;
this.generateImageEls();
this.bindEvents();
}
generateImageEls() {
this.imageEls = {};
const viewTypeNames = Object.getOwnPropertyNames(viewTypes);
viewTypeNames.forEach((viewType) => {
this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img');
});
}
bindEvents() {
super.bindEvents();
this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP);
this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE);
this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN);
this.viewModesEls[viewTypes.TWO_UP].addEventListener('click', this.changeToViewTwoUp);
this.viewModesEls[viewTypes.SWIPE].addEventListener('click', this.changeToViewSwipe);
this.viewModesEls[viewTypes.ONION_SKIN].addEventListener('click', this.changeToViewOnionSkin);
}
get imageEl() {
return this.imageEls[this.currentView];
}
get imageFrameEl() {
return this.imageFrameEls[this.currentView];
}
changeView(newView) {
if (!isValidViewType(newView)) {
return;
}
const indicator = imageDiffHelper.removeCommentIndicator(this.imageFrameEl);
this.currentView = newView;
// Clear existing badges on new view
const existingBadges = this.imageFrameEl.querySelectorAll('.badge');
[...existingBadges].map(badge => badge.remove());
// Remove existing references to old view image badges
this.imageBadges = [];
// Image_file.js has a fade animation of 200ms for loading the view
// Need to wait an additional 250ms for the images to be displayed
// on window in order to re-normalize their dimensions
setTimeout(this.renderNewView.bind(this, indicator), 250);
}
renderNewView(indicator) {
// Generate badge coordinates on new view
this.renderBadges();
// Re-render indicator in new view
if (indicator.removed) {
const normalizedIndicator = imageDiffHelper
.resizeCoordinatesToImageElement(this.imageEl, {
x: indicator.x,
y: indicator.y,
width: indicator.image.width,
height: indicator.image.height,
});
imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
}
}
}
export const viewTypes = {
TWO_UP: 'TWO_UP',
SWIPE: 'SWIPE',
ONION_SKIN: 'ONION_SKIN',
};
export function isValidViewType(validate) {
return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
}
/* eslint-disable import/prefer-default-export */
export function isImageLoaded(element) {
return element.complete && element.naturalHeight !== 0;
}
Loading
Loading
@@ -35,8 +35,6 @@ import './shortcuts_network';
import './templates/issuable_template_selector';
import './templates/issuable_template_selectors';
 
// commit
import './commit/file';
import './commit/image_file';
 
// lib/utils
Loading
Loading
@@ -70,7 +68,6 @@ import './build';
import './build_artifacts';
import './build_variables';
import './ci_lint_editor';
import './commit';
import './commits';
import './compare';
import './compare_autocomplete';
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment