collabora-online/browser/src/control/Control.PartsPreview.js

848 lines
28 KiB
JavaScript

/* -*- js-indent-level: 8 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* L.Control.PartsPreview
*/
/* global _ app $ Hammer _UNO cool */
L.Control.PartsPreview = L.Control.extend({
options: {
fetchThumbnail: true,
autoUpdate: true,
imageClass: '',
frameClass: '',
axis: '',
allowOrientation: true,
maxWidth: window.mode.isDesktop() ? 180: (window.mode.isTablet() ? 120: 60),
maxHeight: window.mode.isDesktop() ? 180: (window.mode.isTablet() ? 120: 60)
},
partsFocused: false,
initialize: function (container, preview, options) {
L.setOptions(this, options);
if (!container) {
container = L.DomUtil.get('presentation-controls-wrapper');
}
if (!preview) {
preview = L.DomUtil.get('slide-sorter');
}
this._container = container;
this._partsPreviewCont = preview;
this._partsPreviewCont.onscroll = this._onScroll.bind(this);
this._idNum = 0;
this._width = 0;
this._height = 0;
},
onAdd: function (map) {
this._previewInitialized = false;
this._previewTiles = [];
this._direction = this.options.allowOrientation ?
(!window.mode.isDesktop() && L.DomUtil.isPortrait() ? 'x' : 'y') :
this.options.axis;
this._scrollY = 0;
map.on('updateparts', this._updateDisabled, this);
map.on('updatepart', this._updatePart, this);
map.on('invalidateparts', this._invalidateParts, this);
map.on('tilepreview', this._updatePreview, this);
map.on('insertpage', this._insertPreview, this);
map.on('deletepage', this._deletePreview, this);
map.on('scrolllimits', this._invalidateParts, this);
map.on('scrolltopart', this._scrollToPart, this);
window.addEventListener('resize', L.bind(this._resize, this));
},
createScrollbar: function () {
this._partsPreviewCont.style.whiteSpace = 'nowrap';
},
_updateDisabled: function (e) {
var parts = e.parts;
var selectedPart = e.selectedPart;
var selectedParts = e.selectedParts;
var docType = e.docType;
if (docType === 'text' || isNaN(parts)) {
return;
}
if (docType === 'presentation' || docType === 'drawing') {
if (!this._previewInitialized)
{
// make room for the preview
var docContainer = this._map.options.documentContainer;
if (!L.DomUtil.hasClass(docContainer, 'parts-preview-document')) {
L.DomUtil.addClass(docContainer, 'parts-preview-document');
setTimeout(L.bind(function () {
this._map.invalidateSize();
}, this), 500);
}
this._setPreviewContainerTop();
// Add a special frame just as a drop-site for reordering.
var frameClass = 'preview-frame ' + this.options.frameClass;
var frame = L.DomUtil.create('div', frameClass, this._partsPreviewCont);
this._addDnDHandlers(frame);
frame.setAttribute('draggable', false);
frame.setAttribute('id', 'first-drop-site');
if (window.mode.isDesktop()) {
L.DomUtil.setStyle(frame, 'height', '20px');
L.DomUtil.setStyle(frame, 'margin', '0em');
}
// Create the preview parts
for (var i = 0; i < parts; i++) {
this._previewTiles.push(this._createPreview(i, e.partNames[i]));
}
if (!app.file.fileBasedView)
L.DomUtil.addClass(this._previewTiles[selectedPart], 'preview-img-currentpart');
this._onScroll(); // Load previews.
this._previewInitialized = true;
}
else
{
if (e.partNames !== undefined) {
this._syncPreviews(e);
}
if (!app.file.fileBasedView) {
// change the border style of the selected preview.
for (var j = 0; j < parts; j++) {
L.DomUtil.removeClass(this._previewTiles[j], 'preview-img-currentpart');
L.DomUtil.removeClass(this._previewTiles[j], 'preview-img-selectedpart');
if (j === selectedPart)
L.DomUtil.addClass(this._previewTiles[j], 'preview-img-currentpart');
else if (selectedParts.indexOf(j) >= 0)
L.DomUtil.addClass(this._previewTiles[j], 'preview-img-selectedpart');
}
}
}
if (!this.options.allowOrientation) {
return;
}
// update portrait / landscape
var removePreviewImg = 'preview-img-portrait';
var addPreviewImg = 'preview-img-landscape';
var removePreviewFrame = 'preview-frame-portrait';
var addPreviewFrame = 'preview-frame-landscape';
if (L.DomUtil.isPortrait()) {
removePreviewImg = 'preview-img-landscape';
addPreviewImg = 'preview-img-portrait';
removePreviewFrame = 'preview-frame-landscape';
addPreviewFrame = 'preview-frame-portrait';
}
for (i = 0; i < parts; i++) {
L.DomUtil.removeClass(this._previewTiles[i], removePreviewImg);
L.DomUtil.addClass(this._previewTiles[i], addPreviewImg);
if (this._map._docLayer._hiddenSlides.has(i))
L.DomUtil.addClass(this._previewTiles[i], 'hidden-slide');
else
L.DomUtil.removeClass(this._previewTiles[i], 'hidden-slide');
}
var previewFrame = $(this._partsPreviewCont).find('.preview-frame');
previewFrame.removeClass(removePreviewFrame);
previewFrame.addClass(addPreviewFrame);
// re-create scrollbar with new direction
this._direction = !window.mode.isDesktop() && !window.mode.isTablet() && L.DomUtil.isPortrait() ? 'x' : 'y';
}
},
_createPreview: function (i, hashCode) {
var frameClass = 'preview-frame ' + this.options.frameClass;
var frame = L.DomUtil.create('div', frameClass, this._partsPreviewCont);
frame.id = 'preview-frame-part-' + this._idNum;
this._addDnDHandlers(frame);
L.DomUtil.create('span', 'preview-helper', frame);
var imgClassName = 'preview-img ' + this.options.imageClass;
var img = L.DomUtil.create('img', imgClassName, frame);
img.setAttribute('alt', _('preview of page ') + String(i + 1));
img.id = 'preview-img-part-' + this._idNum;
img.hash = hashCode;
L.LOUtil.setImage(img, 'preview_placeholder.svg', this._map);
img.fetched = false;
if (!window.mode.isDesktop()) {
(new Hammer(img, {recognizers: [[Hammer.Press]]}))
.on('press', function (e) {
if (this._map.isEditMode()) {
this._addDnDTouchHandlers(e);
}
}.bind(this));
}
L.DomEvent.on(img, 'click', function (e) {
L.DomEvent.stopPropagation(e);
L.DomEvent.stop(e);
var part = this._findClickedPart(e.target.parentNode);
if (part !== null)
var partId = parseInt(part) - 1; // The first part is just a drop-site for reordering.
if (!window.mode.isDesktop() && partId === this._map._docLayer._selectedPart && !app.file.fileBasedView) {
// if mobile or tab then second tap will open the mobile wizard
if (this._map._permission === 'edit') {
// Remove selection to get the slide properties in mobile wizard.
app.socket.sendMessage('resetselection');
setTimeout(function () {
app.dispatcher.dispatch('mobile_wizard');
}, 0);
}
} else {
this._setPart(e);
this._map.focus();
this.partsFocused = true;
if (!window.mode.isDesktop()) {
// needed so on-screen keyboard doesn't pop up when switching slides,
// but would cause PgUp/Down to not work on desktop in slide sorter
document.activeElement.blur();
}
}
if (app.file.fileBasedView)
this._map._docLayer._checkSelectedPart();
}, this);
var that = this;
var pcw = document.getElementById('presentation-controls-wrapper');
L.DomEvent.on(pcw, 'contextmenu', function(e) {
var isMasterView = this._map['stateChangeHandler'].getItemValue('.uno:SlideMasterPage');
var $trigger = $(pcw);
if (isMasterView === 'true') {
$trigger.contextMenu(false);
return;
}
$trigger.contextMenu(true);
that._setPart(e);
$.contextMenu({
selector: '#presentation-controls-wrapper',
className: 'cool-font',
items: {
paste: {
name: _('Paste Slide'),
callback: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
if (part !== null) {
that._setPart(that.copiedSlide);
that._map.duplicatePage(parseInt(part));
}
},
visible: function() {
return that.copiedSlide;
}
},
newslide: {
name: _UNO(that._map._docLayer._docType == 'presentation' ? '.uno:InsertSlide' : '.uno:InsertPage', 'presentation'),
callback: function() { that._map.insertPage(); }
}
}
});
}, this);
L.DomEvent.on(img, 'contextmenu', function(e) {
var isMasterView = this._map['stateChangeHandler'].getItemValue('.uno:SlideMasterPage');
var $trigger = $('#' + img.id);
if (isMasterView === 'true') {
$trigger.contextMenu(false);
return;
}
$trigger.contextMenu(true);
that._setPart(e);
$.contextMenu({
selector: '#' + img.id,
className: 'cool-font',
items: {
copy: {
name: _('Copy'),
callback: function() {
that.copiedSlide = e;
},
visible: function() {
return true;
}
},
paste: {
name: _('Paste'),
callback: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
if (part !== null) {
that._setPart(that.copiedSlide);
that._map.duplicatePage(parseInt(part));
}
},
visible: function() {
return that.copiedSlide;
}
},
newslide: {
name: _UNO(that._map._docLayer._docType == 'presentation' ? '.uno:InsertSlide' : '.uno:InsertPage', 'presentation'),
callback: function() { that._map.insertPage(); }
},
duplicateslide: {
name: _UNO(that._map._docLayer._docType == 'presentation' ? '.uno:DuplicateSlide' : '.uno:DuplicatePage', 'presentation'),
callback: function() { that._map.duplicatePage(); }
},
delete: {
name: _UNO(that._map._docLayer._docType == 'presentation' ? '.uno:DeleteSlide' : '.uno:DeletePage', 'presentation'),
callback: function() { app.dispatcher.dispatch('deletepage'); },
visible: function() {
return that._map._docLayer._parts > 1;
}
},
slideproperties: {
name: _UNO(that._map._docLayer._docType == 'presentation' ? '.uno:SlideSetup' : '.uno:PageSetup', 'presentation'),
callback: function() {
app.socket.sendMessage('uno .uno:PageSetup');
}
},
showslide: {
name: _UNO('.uno:ShowSlide', 'presentation'),
callback: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
if (part !== null) {
that._map.showSlide();
}
},
visible: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
return that._map._docLayer._docType == 'presentation' && that._map._docLayer.isHiddenSlide(parseInt(part) - 1);
}
},
hideslide: {
name: _UNO('.uno:HideSlide', 'presentation'),
callback: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
if (part !== null) {
that._map.hideSlide();
}
},
visible: function(key, options) {
var part = that._findClickedPart(options.$trigger[0].parentNode);
return that._map._docLayer._docType == 'presentation' && !that._map._docLayer.isHiddenSlide(parseInt(part) - 1);
}
}
}
});
}, this);
var imgSize = this._map.getPreview(i, i,
this.options.maxWidth,
this.options.maxHeight,
{autoUpdate: this.options.autoUpdate,
fetchThumbnail: false});
L.DomUtil.setStyle(img, 'width', imgSize.width + 'px');
L.DomUtil.setStyle(img, 'height', imgSize.height + 'px');
this._idNum++;
return img;
},
_setPreviewContainerTop: function () {
var previewContBB = this._partsPreviewCont.getBoundingClientRect();
if (this._direction === 'x') {
this._previewContTop = previewContBB.left;
} else {
this._previewContTop = previewContBB.top;
}
},
_scrollToPart: function() {
var partNo = this._map.getCurrentPartNumber();
// update the page back and forward buttons status
var pagerButtonsEvent = { selectedPart: partNo, parts: this._partsPreviewCont.children.length };
window.onUpdateParts(pagerButtonsEvent);
//var sliderSize, nodePos, nodeOffset, nodeMargin;
var node = this._partsPreviewCont.children[partNo];
if (node && (!this._previewTiles[partNo] || !this._isPreviewVisible(partNo))) {
var nodePos = this._direction === 'x' ? $(node).position().left : $(node).position().top;
var scrollDirection = window.mode.isDesktop() || window.mode.isTablet() ? 'scrollTop': (L.DomUtil.isPortrait() ? 'scrollLeft': 'scrollTop');
var that = this;
if (this._map._partsDirection < 0) {
setTimeout(function() {
that._partsPreviewCont[scrollDirection] += nodePos;
}, 50);
} else {
setTimeout(function() {
that._partsPreviewCont[scrollDirection] += nodePos;
}, 50);
}
}
},
// We will use this function because IE doesn't support "Array.from" feature.
_findClickedPart: function (element) {
for (var i = 0; i < this._partsPreviewCont.children.length; i++) {
if (this._partsPreviewCont.children[i] === element) {
return i;
}
}
return -1;
},
// This is used with fileBasedView.
_scrollViewToPartPosition: function (partNumber, fromBottom) {
if (this._map._docLayer && this._map._docLayer._isZooming)
return;
var ratio = this._map._docLayer._tileSize / this._map._docLayer._tileHeightTwips;
var partHeightPixels = Math.round((this._map._docLayer._partHeightTwips + this._map._docLayer._spaceBetweenParts) * ratio);
var scrollTop = partHeightPixels * partNumber;
var viewHeight = app.sectionContainer.getViewSize()[1];
if (viewHeight > partHeightPixels && partNumber > 0)
scrollTop -= Math.round((viewHeight - partHeightPixels) * 0.5);
// scroll to the bottom of the selected part/page instead of its top px
if (fromBottom)
scrollTop += partHeightPixels - viewHeight;
scrollTop = Math.round(scrollTop / app.dpiScale);
app.sectionContainer.getSectionWithName(L.CSections.Scroll.name).onScrollTo({x: 0, y: scrollTop});
},
_scrollViewByDirection: function(buttonType) {
if (this._map._docLayer && this._map._docLayer._isZooming)
return;
var ratio = this._map._docLayer._tileSize / this._map._docLayer._tileHeightTwips;
var partHeightPixels = Math.round((this._map._docLayer._partHeightTwips + this._map._docLayer._spaceBetweenParts) * ratio);
var scroll = Math.floor(partHeightPixels / app.dpiScale);
var viewHeight = Math.floor(app.sectionContainer.getViewSize()[1]);
var viewHeightScaled = Math.round(Math.floor(viewHeight) / app.dpiScale);
var scrollBySize = Math.floor(viewHeightScaled * 0.75);
var topPx = (app.sectionContainer.getSectionWithName(L.CSections.Scroll.name).containerObject.getDocumentTopLeft()[1] / app.dpiScale);
if (buttonType === 'prev') {
if (this._map.getCurrentPartNumber() == 0) {
if (topPx - scrollBySize <= 0) {
this._scrollViewToPartPosition(0);
return;
}
}
} else if (buttonType === 'next') {
if (this._map._docLayer._parts == this._map.getCurrentPartNumber() + 1) {
scroll *= this._map.getCurrentPartNumber();
var veryEnd = scroll + (Math.floor(partHeightPixels / app.dpiScale) - viewHeightScaled);
if (topPx + viewHeightScaled >= veryEnd) {
this._scrollViewToPartPosition(this._map.getCurrentPartNumber(), true);
return;
}
}
}
app.sectionContainer.getSectionWithName(L.CSections.Scroll.name).onScrollBy({x: 0, y: buttonType === 'prev' ? -scrollBySize : scrollBySize});
},
_setPart: function (e) {
if (cool.Comment.isAnyEdit()) {
cool.CommentSection.showCommentEditingWarning();
return;
}
var part = this._findClickedPart(e.target.parentNode);
if (part !== null) {
var partId = parseInt(part) - 1; // The first part is just a drop-site for reordering.
if (app.file.fileBasedView) {
this._map.setPart(partId);
this._scrollViewToPartPosition(part - 1);
return;
}
if (e.ctrlKey) {
this._map.selectPart(partId, 2, false); // Toggle selection on ctrl+click.
if (this.firstSelection === undefined)
this.firstSelection = this._map._docLayer._selectedPart;
} else if (e.altKey) {
window.app.console.log('alt');
} else if (e.shiftKey) {
if (this.firstSelection === undefined)
this.firstSelection = this._map._docLayer._selectedPart;
//deselect all slide
this._map.deselectAll();
//reselect the first origianl selection
this._map.setPart(this.firstSelection);
this._map.selectPart(this.firstSelection, 1, false);
if (this.firstSelection < partId) {
for (var id = this.firstSelection + 1; id <= partId; ++id) {
this._map.selectPart(id, 2, false);
}
} else if (this.firstSelection > partId) {
for (id = this.firstSelection - 1; id >= partId; --id) {
this._map.selectPart(id, 2, false);
}
}
} else {
this._map.setPart(partId);
this._map.selectPart(partId, 1, false); // And select.
this.firstSelection = partId;
}
}
},
_updatePart: function (e) {
if ((e.docType === 'presentation' || e.docType === 'drawing') && e.part >= 0) {
this._map.getPreview(e.part, e.part, this.options.maxWidth, this.options.maxHeight, {autoUpdate: this.options.autoUpdate});
}
},
_syncPreviews: function (e) {
var it = 0;
var parts = e.parts;
if (parts !== this._previewTiles.length) {
if (Math.abs(parts - this._previewTiles.length) === 1) {
if (parts > this._previewTiles.length) {
for (it = 0; it < parts; it++) {
if (it === this._previewTiles.length) {
this._insertPreview({selectedPart: it - 1, hashCode: e.partNames[it]});
break;
}
if (this._previewTiles[it].hash !== e.partNames[it]) {
this._insertPreview({selectedPart: it, hashCode: e.partNames[it]});
break;
}
}
}
else {
for (it = 0; it < this._previewTiles.length; it++) {
if (it === e.partNames.length ||
this._previewTiles[it].hash !== e.partNames[it]) {
this._deletePreview({selectedPart: it});
break;
}
}
}
}
else {
// sync all, should never happen
while (this._previewTiles.length < e.partNames.length) {
this._insertPreview({selectedPart: this._previewTiles.length - 1,
hashCode: e.partNames[this._previewTiles.length]});
}
while (this._previewTiles.length > e.partNames.length) {
this._deletePreview({selectedPart: this._previewTiles.length - 1});
}
for (it = 0; it < e.partNames.length; it++) {
this._previewTiles[it].hash = e.partNames[it];
L.LOUtil.setImage(this._previewTiles[it], 'preview_placeholder.svg', this._map);
this._previewTiles[it].fetched = false;
}
}
}
else {
// update hash code when user click insert slide.
for (it = 0; it < parts; it++) {
if (this._previewTiles[it].hash !== e.partNames[it]) {
this._previewTiles[it].hash = e.partNames[it];
this._map.getPreview(it, it, this.options.maxWidth, this.options.maxHeight, {autoUpdate: this.options.autoUpdate});
}
}
}
},
_resize: function () {
if (this._height == window.innerHeight &&
this._width == window.innerWidth)
return;
if (this._previewInitialized) {
clearTimeout(this._resizeTimer);
this._resizeTimer = setTimeout(L.bind(this._onScroll, this), 50);
}
this._height = window.innerHeight;
this._width = window.innerWidth;
},
_updatePreview: function (e) {
if (this._map.isPresentationOrDrawing()) {
this._map._previewRequestsOnFly--;
if (this._map._previewRequestsOnFly < 0) {
this._map._previewRequestsOnFly = 0;
this._map._timeToEmptyQueue = new Date();
}
this._map._processPreviewQueue();
if (!this._previewInitialized)
return;
if (this._previewTiles[e.id]) {
this._previewTiles[e.id].src = e.tile.src;
this._previewTiles[e.id].fetched = true;
window.app.console.debug('PREVIEW: part fetched : ' + e.id);
}
}
},
_insertPreview: function (e) {
if (this._map.isPresentationOrDrawing()) {
var newIndex = e.selectedPart + 1;
var newPreview = this._createPreview(newIndex, (e.hashCode === undefined ? null : e.hashCode));
// insert newPreview to newIndex position
this._previewTiles.splice(newIndex, 0, newPreview);
var selectedFrame = this._previewTiles[e.selectedPart].parentNode;
var newFrame = newPreview.parentNode;
// insert after selectedFrame
selectedFrame.parentNode.insertBefore(newFrame, selectedFrame.nextSibling);
}
},
_deletePreview: function (e) {
if (this._map.isPresentationOrDrawing()) {
var selectedFrame = this._previewTiles[e.selectedPart].parentNode;
L.DomUtil.remove(selectedFrame);
this._previewTiles.splice(e.selectedPart, 1);
}
},
_onScroll: function (e) {
setTimeout(L.bind(function (e) {
var scrollOffset = 0;
if (e) {
var prevScrollY = this._scrollY;
var rectangle = e.target.getBoundingClientRect();
this._scrollY = this._direction === 'x' ? -rectangle.left : -rectangle.top;
scrollOffset = this._scrollY - prevScrollY;
}
var previewContBB = this._partsPreviewCont.getBoundingClientRect();
var extra = this._direction === 'x' ? previewContBB.width : previewContBB.height;
var topBound = this._previewContTop - (scrollOffset < 0 ? extra : extra / 2);
var bottomBound = this._previewContTop + extra + (scrollOffset > 0 ? extra : extra / 2);
for (var i = 0; i < this._previewTiles.length; ++i) {
var img = this._previewTiles[i];
if (img && img.parentNode && !img.fetched) {
var previewFrameBB = img.parentNode.getBoundingClientRect();
if (this._direction === 'x') {
if ((previewFrameBB.left >= topBound && previewFrameBB.left <= bottomBound)
|| (previewFrameBB.right >= topBound && previewFrameBB.right <= bottomBound)) {
this._map.getPreview(i, i, this.options.maxWidth, this.options.maxHeight, {autoUpdate: this.options.autoUpdate});
}
} else if ((previewFrameBB.top >= topBound && previewFrameBB.top <= bottomBound)
|| (previewFrameBB.bottom >= topBound && previewFrameBB.bottom <= bottomBound)) {
this._map.getPreview(i, i, this.options.maxWidth, this.options.maxHeight, {autoUpdate: this.options.autoUpdate});
}
}
}
}, this, e), 0);
},
_isPreviewVisible: function(part) {
var el = this._previewTiles[part];
if (!el)
return false;
var elemRect = el.getBoundingClientRect();
var viewRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);
return (elemRect.left <= viewRect.right &&
viewRect.left <= elemRect.right &&
elemRect.top <= viewRect.bottom &&
viewRect.top <= elemRect.bottom)
},
_addDnDHandlers: function (elem) {
if (app.file.fileBasedView) // No drag & drop for pdf files and the like.
return;
if (elem) {
elem.setAttribute('draggable', true);
elem.addEventListener('dragstart', this._handleDragStart, false);
elem.addEventListener('dragenter', this._handleDragEnter, false);
elem.addEventListener('dragover', this._handleDragOver, false);
elem.addEventListener('dragleave', this._handleDragLeave, false);
elem.addEventListener('drop', this._handleDrop, false);
elem.addEventListener('dragend', this._handleDragEnd, false);
elem.partsPreview = this;
}
},
_addDnDTouchHandlers: function (e) {
$(e.target).bind('touchmove', this._handleTouchMove.bind(this));
$(e.target).bind('touchcancel', this._handleTouchCancel.bind(this));
$(e.target).bind('touchend', this._handleTouchEnd.bind(this));
// To avoid having to add a new message to move an arbitrary part, let's select the
// slide that is being dragged.
var part = this._findClickedPart(e.target.parentNode);
if (part !== null) {
var partId = parseInt(part) - 1; // The first part is just a drop-site for reordering.
this._map.setPart(partId);
this._map.selectPart(partId, 1, false); // And select.
}
this.draggedSlide = L.DomUtil.create('img', '', document.body);
this.draggedSlide.setAttribute('src', e.target.currentSrc);
$(this.draggedSlide).css('position', 'absolute');
$(this.draggedSlide).css('height', e.target.height);
$(this.draggedSlide).css('width', e.target.width);
$(this.draggedSlide).css('left', e.center.x - (e.target.width/2));
$(this.draggedSlide).css('top', e.center.y - e.target.height);
$(this.draggedSlide).css('z-index', '10');
$(this.draggedSlide).css('opacity', '75%');
$(this.draggedSlide).css('pointer-events', 'none');
$('.preview-img').css('pointer-events', 'none');
this.currentNode = null;
this.previousNode = null;
},
_removeDnDTouchHandlers: function (e) {
$(e.target).unbind('touchmove');
$(e.target).unbind('touchcancel');
$(e.target).unbind('touchend');
$('.preview-img').css('pointer-events', '');
},
_handleTouchMove: function (e) {
if (e.preventDefault) {
e.preventDefault();
}
this.currentNode = document.elementFromPoint(e.originalEvent.touches[0].clientX, e.originalEvent.touches[0].clientY);
if (this.currentNode !== this.previousNode && this.previousNode !== null) {
$('.preview-frame').removeClass('preview-img-dropsite');
}
if (this.currentNode.draggable || this.currentNode.id === 'first-drop-site') {
this.currentNode.classList.add('preview-img-dropsite');
}
this.previousNode = this.currentNode;
$(this.draggedSlide).css('left', e.originalEvent.touches[0].clientX - (e.target.width/2));
$(this.draggedSlide).css('top', e.originalEvent.touches[0].clientY - e.target.height);
return false;
},
_handleTouchCancel: function(e) {
$('.preview-frame').removeClass('preview-img-dropsite');
$(this.draggedSlide).remove();
this._removeDnDTouchHandlers(e);
},
_handleTouchEnd: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
}
if (this.currentNode) {
var part = this._findClickedPart(this.currentNode);
if (part !== null) {
var partId = parseInt(part) - 1; // First frame is a drop-site for reordering.
if (partId < 0)
partId = -1; // First item is -1.
app.socket.sendMessage('moveselectedclientparts position=' + partId);
}
}
$('.preview-frame').removeClass('preview-img-dropsite');
$(this.draggedSlide).remove();
this._removeDnDTouchHandlers(e);
return false;
},
_handleDragStart: function (e) {
// To avoid having to add a new message to move an arbitrary part, let's select the
// slide that is being dragged.
var part = this.partsPreview._findClickedPart(e.target.parentNode);
if (part !== null) {
var partId = parseInt(part) - 1; // The first part is just a drop-site for reordering.
if (this.partsPreview._map._docLayer && !this.partsPreview._map._docLayer._selectedParts.indexOf(partId) >= 0)
{
this.partsPreview._map.setPart(partId);
this.partsPreview._map.selectPart(partId, 1, false); // And select.
}
}
// By default we move when dragging, but can
// support duplication with ctrl in the future.
e.dataTransfer.effectAllowed = 'move';
},
_handleDragOver: function (e) {
if (e.preventDefault) {
e.preventDefault();
}
// By default we move when dragging, but can
// support duplication with ctrl in the future.
e.dataTransfer.dropEffect = 'move';
this.classList.add('preview-img-dropsite');
return false;
},
_handleDragEnter: function () {
},
_handleDragLeave: function () {
this.classList.remove('preview-img-dropsite');
},
_handleDrop: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
}
// When dropping on a thumbnail we get an `img` tag as a target, so we need to get the
// parent.
// Otherwise dropping between slides doesn't work.
// See https://github.com/CollaboraOnline/online/issues/6941
var target = e.target.classList.contains('preview-img') ? e.target.parentNode : e.target;
var part = this.partsPreview._findClickedPart(target);
if (part !== null) {
var partId = parseInt(part) - 1; // First frame is a drop-site for reordering.
if (partId < 0)
partId = -1; // First item is -1.
app.socket.sendMessage('moveselectedclientparts position=' + partId);
}
this.classList.remove('preview-img-dropsite');
return false;
},
_handleDragEnd: function () {
this.classList.remove('preview-img-dropsite');
},
_invalidateParts: function () {
if (!this._container ||
!this._partsPreviewCont ||
!this._previewInitialized ||
!this._previewTiles)
return;
for (var part = 0; part < this._previewTiles.length; part++) {
this._map.getPreview(part, part,
this.options.maxWidth,
this.options.maxHeight,
{autoUpdate: this.options.autoUpdate,
fetchThumbnail: this.options.fetchThumbnail});
}
},
});
L.control.partsPreview = function (container, preview, options) {
return new L.Control.PartsPreview(container, preview, options);
};