1558 lines
31 KiB
JavaScript
1558 lines
31 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
BX.namespace('BX.UI.Viewer');
|
|
|
|
BX.UI.Viewer.Item = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
/**
|
|
* @type {BX.UI.Viewer.Controller}
|
|
*/
|
|
this.controller = null;
|
|
this.title = options.title;
|
|
this.src = options.src;
|
|
this.nakedActions = BX.Type.isArrayFilled(options.nakedActions) ? options.nakedActions : [];
|
|
this.actions = BX.Type.isArrayFilled(options.actions) ? options.actions : [];
|
|
this.contentType = options.contentType;
|
|
this.isLoaded = false;
|
|
this.isLoading = false;
|
|
this.isTransforming = false;
|
|
this.isTransformationError = false;
|
|
this.sourceNode = null;
|
|
this.transformationTimeoutId = null;
|
|
this.longPollingTimeoutId = null;
|
|
this.viewerGroupBy = null;
|
|
this.previewUrl = null;
|
|
this.downloadUrl = null;
|
|
this.isSeparate = false;
|
|
this.transformationTimeout = options.transformationTimeout || 22000;
|
|
this.layout = {
|
|
container: null,
|
|
};
|
|
|
|
this.onTransformationComplete = this.handleTransformationComplete.bind(this);
|
|
this.options = options;
|
|
|
|
this.init();
|
|
};
|
|
|
|
BX.UI.Viewer.Item.prototype = {
|
|
setController(controller)
|
|
{
|
|
if (!(controller instanceof BX.UI.Viewer.Controller))
|
|
{
|
|
throw new TypeError("BX.UI.Viewer.Item: 'controller' has to be instance of BX.UI.Viewer.Controller.");
|
|
}
|
|
|
|
this.controller = controller;
|
|
},
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
this.title = node.dataset.title || node.title || node.alt;
|
|
this.src = node.dataset.src;
|
|
this.viewerGroupBy = node.dataset.viewerGroupBy;
|
|
this.isSeparate = node.dataset.viewerSeparateItem || false;
|
|
this.nakedActions = node.dataset.actions ? JSON.parse(node.dataset.actions) : [];
|
|
|
|
let previewUrl = null;
|
|
if (BX.Type.isStringFilled(node.dataset.viewerPreview))
|
|
{
|
|
previewUrl = node.dataset.viewerPreview;
|
|
}
|
|
else if (BX.Type.isStringFilled(node.dataset.bxPreview))
|
|
{
|
|
previewUrl = node.dataset.bxPreview;
|
|
}
|
|
else if (BX.Type.isStringFilled(node.dataset.thumbSrc))
|
|
{
|
|
previewUrl = node.dataset.thumbSrc;
|
|
}
|
|
else if (this instanceof BX.UI.Viewer.Image && BX.Type.isStringFilled(node.src))
|
|
{
|
|
previewUrl = node.src;
|
|
}
|
|
|
|
this.previewUrl = previewUrl === null || previewUrl.startsWith('data:image') ? null : previewUrl;
|
|
},
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
bindSourceNode(node)
|
|
{
|
|
this.sourceNode = node;
|
|
},
|
|
|
|
applyReloadOptions(options)
|
|
{},
|
|
|
|
isSeparateItem()
|
|
{
|
|
return this.isSeparate;
|
|
},
|
|
|
|
isPullConnected()
|
|
{
|
|
if (top.BX.PULL)
|
|
{
|
|
// pull_v2
|
|
if (BX.type.isFunction(top.BX.PULL.isConnected))
|
|
{
|
|
return top.BX.PULL.isConnected();
|
|
}
|
|
|
|
const debugInfo = top.BX.PULL.getDebugInfoArray();
|
|
|
|
return debugInfo.connected;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
registerTransformationHandler(pullTag)
|
|
{
|
|
if (this.isLoaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.controller.getCurrentItem() === this)
|
|
{
|
|
this.controller.setTextOnLoading(BX.message('JS_UI_VIEWER_ITEM_TRANSFORMATION_IN_PROGRESS'));
|
|
}
|
|
|
|
if (this.isPullConnected())
|
|
{
|
|
BX.Event.EventEmitter.subscribe('onPullEvent-main', this.onTransformationComplete);
|
|
console.log('BX.PULL.extendWatch');
|
|
BX.PULL.extendWatch(pullTag);
|
|
}
|
|
else
|
|
{
|
|
this.resetLongPollingTimeout();
|
|
this.longPollingTimeoutId = setTimeout(() => {
|
|
BX.ajax.promise({
|
|
url: BX.util.add_url_param(this.src, { ts: 'bxviewer' }),
|
|
method: 'GET',
|
|
dataType: 'json',
|
|
headers: [{
|
|
name: 'BX-Viewer-check-transformation',
|
|
value: null,
|
|
}],
|
|
}).then((response) => {
|
|
if (!response.data || !response.data.transformation)
|
|
{
|
|
this.registerTransformationHandler();
|
|
}
|
|
else
|
|
{
|
|
this.controller.reload(this, {
|
|
forceTransformation: true,
|
|
});
|
|
}
|
|
});
|
|
}, 5000);
|
|
}
|
|
|
|
if (this.transformationTimeoutId === null && this.isTransformationError === false)
|
|
{
|
|
this.transformationTimeoutId = setTimeout(() => {
|
|
if (this.isLoading)
|
|
{
|
|
console.log('Throw transformationTimeout');
|
|
if (this._loadPromise)
|
|
{
|
|
this._loadPromise.reject({
|
|
status: 'timeout',
|
|
message: BX.message('JS_UI_VIEWER_ITEM_TRANSFORMATION_ERROR_1').replace('#DOWNLOAD_LINK#', this.getSrc()),
|
|
item: this,
|
|
});
|
|
|
|
this.isLoading = false;
|
|
this.isTransformationError = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.log('We don\'t have transformationTimeout :) ');
|
|
}
|
|
}, this.transformationTimeout);
|
|
}
|
|
},
|
|
|
|
handleTransformationComplete(event)
|
|
{
|
|
const [command] = event.getCompatData();
|
|
if (command === 'transformationComplete' && this.isTransforming)
|
|
{
|
|
this.controller.reload(this, {
|
|
forceTransformation: true,
|
|
});
|
|
}
|
|
},
|
|
|
|
resetTransformationTimeout()
|
|
{
|
|
if (this.transformationTimeoutId)
|
|
{
|
|
clearTimeout(this.transformationTimeoutId);
|
|
}
|
|
|
|
this.transformationTimeoutId = null;
|
|
},
|
|
|
|
resetLongPollingTimeout()
|
|
{
|
|
if (this.longPollingTimeoutId)
|
|
{
|
|
clearTimeout(this.longPollingTimeoutId);
|
|
}
|
|
|
|
this.longPollingTimeoutId = null;
|
|
},
|
|
|
|
init()
|
|
{},
|
|
|
|
load()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
if (this.isLoaded)
|
|
{
|
|
promise.fulfill(this);
|
|
console.log('isLoaded');
|
|
|
|
return promise;
|
|
}
|
|
|
|
if (this.isTransformationError)
|
|
{
|
|
promise.reject({
|
|
item: this,
|
|
});
|
|
|
|
return promise;
|
|
}
|
|
|
|
if (this.isLoading)
|
|
{
|
|
console.log('isLoading');
|
|
|
|
if (this.isTransforming && this.controller.getCurrentItem() === this)
|
|
{
|
|
this.controller.setTextOnLoading(BX.Loc.getMessage('JS_UI_VIEWER_ITEM_TRANSFORMATION_IN_PROGRESS'));
|
|
}
|
|
|
|
return this._loadPromise;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
this._loadPromise = this.loadData().then((item) => {
|
|
this.isLoaded = true;
|
|
this.isLoading = false;
|
|
this.isTransforming = false;
|
|
|
|
return item;
|
|
}).catch((reason) => {
|
|
console.log('catch');
|
|
this.isLoaded = false;
|
|
this.isLoading = false;
|
|
this.isTransforming = false;
|
|
|
|
if (!reason.item)
|
|
{
|
|
reason.item = this;
|
|
}
|
|
|
|
const promise = new BX.Promise();
|
|
promise.reject(reason);
|
|
|
|
return promise;
|
|
});
|
|
|
|
console.log('will load');
|
|
|
|
return this._loadPromise;
|
|
},
|
|
|
|
/**
|
|
* Returns list of classes which will be added to viewer container before showing
|
|
* and will be deleted after hiding.
|
|
* @return {Array}
|
|
*/
|
|
listContainerModifiers()
|
|
{
|
|
return [];
|
|
},
|
|
|
|
getSrc()
|
|
{
|
|
return this.src;
|
|
},
|
|
|
|
hashCode(string)
|
|
{
|
|
let h = 0; const l = string.length; let
|
|
i = 0;
|
|
if (l > 0)
|
|
{
|
|
while (i < l)
|
|
|
|
{ h = (h << 5) - h + string.charCodeAt(i++) | 0;
|
|
}
|
|
}
|
|
|
|
return h;
|
|
},
|
|
|
|
generateUniqueId()
|
|
{
|
|
return this.hashCode(this.getSrc() || '') + (Math.floor(Math.random() * Math.floor(10000)));
|
|
},
|
|
|
|
getTitle()
|
|
{
|
|
return this.title;
|
|
},
|
|
|
|
getPreviewUrl()
|
|
{
|
|
return this.previewUrl;
|
|
},
|
|
|
|
getDownloadUrl()
|
|
{
|
|
return this.downloadUrl === null ? this.src : this.downloadUrl;
|
|
},
|
|
|
|
setDownloadUrl(url)
|
|
{
|
|
if (BX.Type.isStringFilled(url) || url === null)
|
|
{
|
|
this.downloadUrl = url;
|
|
}
|
|
},
|
|
|
|
getGroupBy()
|
|
{
|
|
return this.viewerGroupBy;
|
|
},
|
|
|
|
getNakedActions()
|
|
{
|
|
return this.nakedActions;
|
|
},
|
|
|
|
setActions(actions)
|
|
{
|
|
this.actions = actions;
|
|
},
|
|
|
|
getActions()
|
|
{
|
|
return this.actions;
|
|
},
|
|
|
|
/**
|
|
* @returns {BX.Promise}
|
|
*/
|
|
loadData()
|
|
{
|
|
const promise = new BX.Promise();
|
|
promise.setAutoResolve(true);
|
|
promise.fulfill(this);
|
|
|
|
return promise;
|
|
},
|
|
|
|
render()
|
|
{},
|
|
|
|
renderExtraActions()
|
|
{},
|
|
|
|
getMoreMenuItems()
|
|
{
|
|
return [];
|
|
},
|
|
|
|
/**
|
|
* @returns {BX.Promise}
|
|
*/
|
|
getContentWidth()
|
|
{},
|
|
|
|
handleKeyPress(event)
|
|
{},
|
|
|
|
handleClickOnItemContainer(event)
|
|
{},
|
|
|
|
handleResize()
|
|
{},
|
|
|
|
asFirstToShow()
|
|
{},
|
|
|
|
afterRender()
|
|
{},
|
|
|
|
beforeHide()
|
|
{},
|
|
|
|
destroy()
|
|
{
|
|
this.resetTransformationTimeout();
|
|
this.resetLongPollingTimeout();
|
|
BX.Event.EventEmitter.unsubscribe('onPullEvent-main', this.onTransformationComplete);
|
|
},
|
|
|
|
abort()
|
|
{
|
|
// Implement this method if an item loading can be aborted
|
|
return false;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.Image = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
|
|
this.resizedSrc = options.resizedSrc;
|
|
this.width = options.width;
|
|
this.height = options.height;
|
|
|
|
this.scale = 1;
|
|
this.rotation = 0;
|
|
this.translate = { x: 0, y: 0 };
|
|
this.panning = false;
|
|
|
|
/**
|
|
* @type {HTMLImageElement}
|
|
*/
|
|
this.imageNode = null;
|
|
this.layout = {
|
|
container: null,
|
|
actions: null,
|
|
};
|
|
|
|
this.xhr = null;
|
|
|
|
this.onPointerDownHandler = null;
|
|
this.onPointerMoveHandler = null;
|
|
this.onPointerUpHandler = null;
|
|
};
|
|
|
|
BX.UI.Viewer.Image.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
BX.UI.Viewer.Item.prototype.setPropertiesByNode.apply(this, arguments);
|
|
|
|
this.src = node.dataset.src || node.src;
|
|
this.width = node.dataset.width;
|
|
this.height = node.dataset.height;
|
|
|
|
if (!BX.Type.isUndefined(node.dataset.viewerResized))
|
|
{
|
|
this.resizedSrc = this.src;
|
|
}
|
|
},
|
|
|
|
applyReloadOptions(options)
|
|
{
|
|
this.controller.unsetCachedData(this.src);
|
|
},
|
|
|
|
tryToExportResizedSrcFromSourceNode()
|
|
{
|
|
/**
|
|
* @see .ui-viewer-inner-content-wrapper > * {
|
|
* max-height: calc(100% - 210px)
|
|
*/
|
|
const paddingHeight = 210;
|
|
if (!(this.sourceNode instanceof Image))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!this.sourceNode.naturalWidth || this.sourceNode.src.startsWith('data:image'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.sourceNode.src === this.src)
|
|
{
|
|
this.resizedSrc = this.src;
|
|
}
|
|
else if (!this.sourceNode.src.endsWith('.gif') && !this.sourceNode.src.endsWith('.webp'))
|
|
{
|
|
const offsetHeight = this.controller.getItemContainer().offsetHeight;
|
|
const offsetWidth = this.controller.getItemContainer().offsetWidth;
|
|
const scale = offsetHeight / offsetWidth;
|
|
const realMaxHeight = (offsetHeight - paddingHeight);
|
|
const realMaxWidth = realMaxHeight / scale;
|
|
|
|
if (this.sourceNode.naturalWidth >= realMaxWidth || this.sourceNode.naturalHeight >= realMaxHeight)
|
|
{
|
|
this.resizedSrc = this.sourceNode.src;
|
|
}
|
|
}
|
|
},
|
|
|
|
loadData()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
if (!BX.Type.isStringFilled(this.resizedSrc))
|
|
{
|
|
if (!this.shouldRunLocalResize())
|
|
{
|
|
this.resizedSrc = this.src;
|
|
}
|
|
else
|
|
{
|
|
this.tryToExportResizedSrcFromSourceNode();
|
|
|
|
if (this.controller.getCachedData(this.src))
|
|
{
|
|
this.resizedSrc = this.controller.getCachedData(this.src).resizedSrc;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.resizedSrc)
|
|
{
|
|
this.imageNode = new Image();
|
|
this.imageNode.className = 'ui-viewer-image';
|
|
this.imageNode.draggable = false;
|
|
this.imageNode.onload = () => {
|
|
promise.fulfill(this);
|
|
};
|
|
|
|
this.imageNode.onerror = this.imageNode.onabort = (event) => {
|
|
console.log('reject');
|
|
promise.reject({
|
|
item: this,
|
|
type: 'error',
|
|
});
|
|
};
|
|
|
|
this.imageNode.src = this.resizedSrc;
|
|
}
|
|
else
|
|
{
|
|
this.xhr = new XMLHttpRequest();
|
|
this.xhr.onreadystatechange = ()=> {
|
|
if (this.xhr.readyState !== XMLHttpRequest.DONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (
|
|
(this.xhr.status === 200 || this.xhr.status === 0)
|
|
&& BX.Type.isBlob(this.xhr.response)
|
|
&& /^image\/[\d.a-z-]+$/i.test(this.xhr.response.type)
|
|
)
|
|
{
|
|
console.log('resize image');
|
|
this.resizedSrc = URL.createObjectURL(this.xhr.response);
|
|
this.imageNode = new Image();
|
|
this.imageNode.className = 'ui-viewer-image';
|
|
this.imageNode.draggable = false;
|
|
this.imageNode.src = this.resizedSrc;
|
|
this.imageNode.onload = () => {
|
|
promise.fulfill(this);
|
|
};
|
|
|
|
this.controller.setCachedData(this.src, { resizedSrc: this.resizedSrc });
|
|
}
|
|
else
|
|
{
|
|
promise.reject({
|
|
item: this,
|
|
type: 'error',
|
|
});
|
|
}
|
|
};
|
|
|
|
this.xhr.open('GET', BX.util.add_url_param(this.src, { ts: 'bxviewer' }), true);
|
|
this.xhr.responseType = 'blob';
|
|
this.xhr.setRequestHeader('BX-Viewer-image', 'x');
|
|
this.xhr.send();
|
|
}
|
|
|
|
return promise;
|
|
},
|
|
|
|
abort()
|
|
{
|
|
if (this.xhr !== null && !this.isLoaded)
|
|
{
|
|
console.log('abort xhr');
|
|
|
|
this.xhr.abort();
|
|
this.xhr = null;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
shouldRunLocalResize()
|
|
{
|
|
return !this.controller.isExternalLink(this.src);
|
|
},
|
|
|
|
render()
|
|
{
|
|
const item = document.createDocumentFragment();
|
|
item.appendChild(this.imageNode);
|
|
this.imageNode.alt = this.title;
|
|
|
|
return item;
|
|
},
|
|
|
|
getMoreMenuItems()
|
|
{
|
|
if (this.title)
|
|
{
|
|
return [{
|
|
text: BX.Loc.getMessage('JS_UI_VIEWER_IMAGE_VIEW_FULL_SIZE_MSGVER_1'),
|
|
href: BX.util.add_url_param(this.src, { ts: 'bxviewer', ibxShowImage: 1 }),
|
|
target: '_blank',
|
|
onclick: () => {
|
|
this.controller.moreMenu?.close();
|
|
},
|
|
}];
|
|
}
|
|
|
|
return [];
|
|
},
|
|
|
|
renderExtraActions()
|
|
{
|
|
if (this.layout.actions === null)
|
|
{
|
|
this.layout.actions = BX.Tag.render`
|
|
<div class="ui-viewer-image-extra-actions">
|
|
<div
|
|
class="ui-viewer-action-btn"
|
|
onclick="${this.handleZoomOut.bind(this)}"
|
|
title="${BX.Text.encode(BX.Loc.getMessage('JS_UI_VIEWER_SINGLE_DOCUMENT_SCALE_ZOOM_OUT'))}"
|
|
>
|
|
<div class="ui-icon-set --zoom-out ui-viewer-action-btn-icon"></div>
|
|
</div>
|
|
<div
|
|
class="ui-viewer-action-btn"
|
|
onclick="${this.handleZoomIn.bind(this)}"
|
|
title="${BX.Text.encode(BX.Loc.getMessage('JS_UI_VIEWER_SINGLE_DOCUMENT_SCALE_ZOOM_IN'))}"
|
|
>
|
|
<div class="ui-icon-set --zoom-in ui-viewer-action-btn-icon"></div>
|
|
</div>
|
|
<div
|
|
class="ui-viewer-action-btn"
|
|
onclick="${this.handleRotate.bind(this)}"
|
|
title="${BX.Text.encode(BX.Loc.getMessage('JS_UI_VIEWER_ITEM_ACTION_ROTATE'))}"
|
|
>
|
|
<div class="ui-icon-set --image-rotate-left ui-viewer-action-btn-icon"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return this.layout.actions;
|
|
},
|
|
|
|
/**
|
|
* @returns {BX.Promise}
|
|
*/
|
|
getContentWidth()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
promise.fulfill(this.imageNode.offsetWidth * this.scale);
|
|
|
|
return promise;
|
|
},
|
|
|
|
afterRender()
|
|
{},
|
|
|
|
enablePanning()
|
|
{
|
|
if (this.panning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.onPointerDownHandler = this.handlePointerDown.bind(this);
|
|
BX.Event.bind(this.imageNode, 'pointerdown', this.onPointerDownHandler);
|
|
|
|
this.panning = true;
|
|
},
|
|
|
|
disablePanning()
|
|
{
|
|
BX.Event.unbind(this.imageNode, 'pointerdown', this.onPointerDownHandler);
|
|
this.onPointerDownHandler = null;
|
|
this.panning = false;
|
|
},
|
|
|
|
handleKeyPress(event)
|
|
{
|
|
if (!this.isLoaded)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (event.code === 'Equal')
|
|
{
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
this.zoomIn();
|
|
|
|
return true;
|
|
}
|
|
|
|
if (event.code === 'Minus')
|
|
{
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
this.zoomOut();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
handleClickOnItemContainer(event)
|
|
{
|
|
if (!this.panning)
|
|
{
|
|
this.controller.showNext();
|
|
}
|
|
},
|
|
|
|
handlePointerDown(event)
|
|
{
|
|
this.onPointerMoveHandler = this.handlePointerMove.bind(this);
|
|
this.onPointerUpHandler = this.handlePointerUp.bind(this);
|
|
|
|
BX.Event.bind(document, 'pointermove', this.onPointerMoveHandler);
|
|
BX.Event.bind(document, 'pointerup', this.onPointerUpHandler);
|
|
},
|
|
|
|
handlePointerMove(event)
|
|
{
|
|
const { movementX, movementY } = event;
|
|
|
|
const windowWidth = window.innerWidth;
|
|
const windowHeight = window.innerHeight;
|
|
|
|
let imageWidth = this.imageNode.offsetWidth * this.scale;
|
|
let imageHeight = this.imageNode.offsetHeight * this.scale;
|
|
const rotated = Math.abs(this.rotation) / 90 % 2 !== 0;
|
|
if (rotated)
|
|
{
|
|
[imageWidth, imageHeight] = [imageHeight, imageWidth];
|
|
}
|
|
|
|
const maxXOffsetX = imageWidth > windowWidth ? (imageWidth - windowWidth) / 2 : 0;
|
|
const maxYOffsetY = imageHeight > windowHeight ? (imageHeight - windowHeight) / 2 : 0;
|
|
|
|
let x = this.translate.x + movementX;
|
|
let y = this.translate.y + movementY;
|
|
if (Math.abs(x) > maxXOffsetX)
|
|
{
|
|
x = maxXOffsetX * Math.sign(x);
|
|
}
|
|
|
|
if (Math.abs(y) > maxYOffsetY)
|
|
{
|
|
y = maxYOffsetY * Math.sign(y);
|
|
}
|
|
|
|
this.translate.x = x;
|
|
this.translate.y = y;
|
|
|
|
this.applyTransform();
|
|
},
|
|
|
|
handlePointerUp()
|
|
{
|
|
BX.Event.unbind(document, 'pointermove', this.onPointerMoveHandler);
|
|
BX.Event.unbind(document, 'pointerup', this.onPointerUpHandler);
|
|
|
|
this.onPointerMoveHandler = null;
|
|
this.onPointerUpHandler = null;
|
|
},
|
|
|
|
applyTransform()
|
|
{
|
|
BX.Dom.style(this.imageNode, 'translate', `${this.translate.x}px ${this.translate.y}px`);
|
|
BX.Dom.style(this.imageNode, 'scale', this.scale);
|
|
BX.Dom.style(this.imageNode, 'rotate', `${this.rotation}deg`);
|
|
|
|
// BX.Dom.style(
|
|
// this.imageNode,
|
|
// 'transform',
|
|
// `translate(${this.translate.x}px, ${this.translate.y}px) scale(${this.scale}) rotate(${this.rotation}deg)`,
|
|
// );
|
|
},
|
|
|
|
resetTranslate()
|
|
{
|
|
this.translate.x = 0;
|
|
this.translate.y = 0;
|
|
},
|
|
|
|
togglePanning()
|
|
{
|
|
if (this.scale > 1 || this.rotation !== 0)
|
|
{
|
|
this.enablePanning();
|
|
}
|
|
else
|
|
{
|
|
this.disablePanning();
|
|
}
|
|
},
|
|
|
|
zoomIn()
|
|
{
|
|
this.scale = Math.min(4, this.scale + 1);
|
|
this.applyTransform();
|
|
this.togglePanning();
|
|
|
|
this.controller.adjustControlsSize(this.getContentWidth());
|
|
},
|
|
|
|
zoomOut()
|
|
{
|
|
this.scale = Math.max(1, this.scale - 1);
|
|
|
|
this.resetTranslate();
|
|
this.applyTransform();
|
|
this.togglePanning();
|
|
|
|
this.controller.adjustControlsSize(this.getContentWidth());
|
|
},
|
|
|
|
rotate()
|
|
{
|
|
this.rotation -= 90;
|
|
|
|
this.resetTranslate();
|
|
this.applyTransform();
|
|
this.togglePanning();
|
|
},
|
|
|
|
handleZoomOut()
|
|
{
|
|
this.zoomOut();
|
|
},
|
|
|
|
handleZoomIn()
|
|
{
|
|
this.zoomIn();
|
|
},
|
|
|
|
handleRotate()
|
|
{
|
|
this.rotate();
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.PlainText = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
|
|
this.content = options.content;
|
|
};
|
|
|
|
BX.UI.Viewer.PlainText.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
BX.UI.Viewer.Item.prototype.setPropertiesByNode.apply(this, arguments);
|
|
|
|
this.content = node.dataset.content;
|
|
},
|
|
|
|
render()
|
|
{
|
|
const contentNode = BX.create('span', {
|
|
text: this.content,
|
|
});
|
|
|
|
contentNode.style.fontSize = '14px';
|
|
contentNode.style.color = 'white';
|
|
|
|
return contentNode;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.Audio = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
|
|
this.playerId = `audio-playerId_${this.generateUniqueId()}`;
|
|
this.svgMask = null;
|
|
};
|
|
|
|
BX.UI.Viewer.Audio.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
BX.UI.Viewer.Item.prototype.setPropertiesByNode.apply(this, arguments);
|
|
|
|
this.playerId = `audio-playerId_${this.generateUniqueId()}`;
|
|
},
|
|
|
|
loadData()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
BX.Runtime.loadExtension('ui.video-player').then(() => {
|
|
const headers = [
|
|
{
|
|
name: 'BX-Viewer-src',
|
|
value: this.src,
|
|
},
|
|
{
|
|
name: 'BX-Viewer',
|
|
value: 'audio',
|
|
},
|
|
];
|
|
|
|
const ajaxPromise = BX.ajax.promise({
|
|
url: BX.util.add_url_param(this.src, { ts: 'bxviewer' }),
|
|
method: 'GET',
|
|
dataType: 'json',
|
|
headers,
|
|
});
|
|
|
|
ajaxPromise.then((response) => {
|
|
if (!response || !response.data)
|
|
{
|
|
const errors = response ? response.errors : [];
|
|
|
|
promise.reject({
|
|
item: this,
|
|
type: 'error',
|
|
errors: errors || [],
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
promise.fulfill(this);
|
|
});
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
|
|
render()
|
|
{
|
|
this.player = new BX.UI.VideoPlayer.Player(this.playerId, {
|
|
width: 320,
|
|
height: 52,
|
|
isAudio: true,
|
|
skin: 'vjs-viewer-audio-player-skin',
|
|
sources: [{
|
|
src: this.src,
|
|
type: 'audio/mp3',
|
|
}],
|
|
});
|
|
|
|
return this.player.createElement();
|
|
},
|
|
|
|
afterRender()
|
|
{
|
|
this.player.init();
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.HightlightCode = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
|
|
this.content = options.content;
|
|
};
|
|
|
|
BX.UI.Viewer.HightlightCode.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
BX.UI.Viewer.Item.prototype.setPropertiesByNode.apply(this, arguments);
|
|
|
|
this.content = node.dataset.content;
|
|
},
|
|
|
|
listContainerModifiers()
|
|
{
|
|
return [
|
|
'ui-viewer-document',
|
|
'ui-viewer-document-hlcode',
|
|
];
|
|
},
|
|
|
|
loadData()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
BX.loadExt('ui.highlightjs').then(() => {
|
|
if (this.content)
|
|
{
|
|
promise.fulfill(this);
|
|
}
|
|
else
|
|
{
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState !== XMLHttpRequest.DONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((xhr.status === 200 || xhr.status === 0) && xhr.response)
|
|
{
|
|
this.content = xhr.response;
|
|
console.log('text content is loaded');
|
|
this.controller.setCachedData(this.src, { content: this.content });
|
|
|
|
promise.fulfill(this);
|
|
}
|
|
else
|
|
{
|
|
promise.reject({
|
|
item: this,
|
|
type: 'error',
|
|
});
|
|
}
|
|
}.bind(this);
|
|
xhr.open('GET', BX.util.add_url_param(this.src, { ts: 'bxviewerText' }), true);
|
|
xhr.responseType = 'text';
|
|
xhr.send();
|
|
}
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
|
|
render()
|
|
{
|
|
const ext = this.getTitle().slice(Math.max(0, this.getTitle().lastIndexOf('.') + 1));
|
|
|
|
return BX.create('div', {
|
|
props: {
|
|
tabIndex: 2208,
|
|
},
|
|
attrs: {
|
|
className: 'ui-viewer-item-document-content',
|
|
},
|
|
style: {
|
|
width: '100%',
|
|
height: '100%',
|
|
paddingTop: '85px',
|
|
background: 'rgba(0, 0, 0, 0.1)',
|
|
overflow: 'auto',
|
|
},
|
|
children: [
|
|
BX.create('pre', {
|
|
children: [
|
|
this.codeNode = BX.create('code', {
|
|
props: {
|
|
className: hljs.getLanguage(ext) ? ext : 'plaintext',
|
|
},
|
|
style: {
|
|
fontSize: '14px',
|
|
textAlign: 'left',
|
|
},
|
|
text: this.content,
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @returns {BX.Promise}
|
|
*/
|
|
getContentWidth()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
promise.fulfill(this.codeNode.offsetWidth);
|
|
|
|
return promise;
|
|
},
|
|
|
|
afterRender()
|
|
{
|
|
hljs.highlightBlock(this.codeNode);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.Unknown = function(options)
|
|
{
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
};
|
|
|
|
BX.UI.Viewer.Unknown.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
render()
|
|
{
|
|
return BX.create('div', {
|
|
props: {
|
|
className: 'ui-viewer-unsupported',
|
|
},
|
|
children: [
|
|
BX.create('div', {
|
|
props: {
|
|
className: 'ui-viewer-unsupported-title',
|
|
},
|
|
text: BX.message('JS_UI_VIEWER_ITEM_UNKNOWN_TITLE'),
|
|
}),
|
|
BX.create('div', {
|
|
props: {
|
|
className: 'ui-viewer-unsupported-text',
|
|
},
|
|
text: BX.message('JS_UI_VIEWER_ITEM_UNKNOWN_NOTICE'),
|
|
}),
|
|
BX.create('a', {
|
|
props: {
|
|
className: 'ui-btn ui-btn-light-border ui-btn-themes',
|
|
href: this.getSrc(),
|
|
target: '_blank',
|
|
},
|
|
text: BX.message('JS_UI_VIEWER_ITEM_UNKNOWN_DOWNLOAD_ACTION'),
|
|
}),
|
|
],
|
|
});
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @extends {BX.UI.Viewer.Item}
|
|
* @param options
|
|
* @constructor
|
|
*/
|
|
BX.UI.Viewer.Video = function(options)
|
|
{
|
|
options = options || {};
|
|
|
|
BX.UI.Viewer.Item.apply(this, arguments);
|
|
|
|
this.player = null;
|
|
this.sources = [];
|
|
this.contentNode = null;
|
|
this.forceTransformation = false;
|
|
this.videoWidth = null;
|
|
this.playerId = `playerId_${this.generateUniqueId()}`;
|
|
};
|
|
|
|
BX.UI.Viewer.Video.prototype = {
|
|
__proto__: BX.UI.Viewer.Item.prototype,
|
|
constructor: BX.UI.Viewer.Item,
|
|
|
|
/**
|
|
* @param {HTMLElement} node
|
|
*/
|
|
setPropertiesByNode(node)
|
|
{
|
|
BX.UI.Viewer.Item.prototype.setPropertiesByNode.apply(this, arguments);
|
|
|
|
this.playerId = `playerId_${this.generateUniqueId()}`;
|
|
|
|
if (!BX.Type.isUndefined(node.dataset.viewerResized))
|
|
{
|
|
this.sources = [
|
|
{ src: node.dataset.src, type: node.dataset.type },
|
|
{ src: node.dataset.src, type: 'video/mp4' },
|
|
];
|
|
this.width = node.dataset.width;
|
|
this.height = node.dataset.height;
|
|
}
|
|
},
|
|
|
|
applyReloadOptions(options)
|
|
{
|
|
if (options.forceTransformation)
|
|
{
|
|
this.forceTransformation = true;
|
|
}
|
|
},
|
|
|
|
loadData()
|
|
{
|
|
const promise = new BX.Promise();
|
|
|
|
BX.Runtime.loadExtension('ui.video-player').then(() => {
|
|
if (this.sources.length > 0)
|
|
{
|
|
promise.fulfill(this);
|
|
return;
|
|
}
|
|
|
|
const headers = [
|
|
{
|
|
name: 'BX-Viewer-src',
|
|
value: this.src,
|
|
},
|
|
];
|
|
|
|
headers.push({
|
|
name: this.forceTransformation ? 'BX-Viewer-force-transformation' : 'BX-Viewer',
|
|
value: 'video',
|
|
});
|
|
|
|
const ajaxPromise = BX.ajax.promise({
|
|
url: BX.util.add_url_param(this.src, { ts: 'bxviewer' }),
|
|
method: 'GET',
|
|
dataType: 'json',
|
|
headers,
|
|
});
|
|
|
|
ajaxPromise.then((response) => {
|
|
if (!response || !response.data)
|
|
{
|
|
const errors = response ? response.errors : [];
|
|
|
|
promise.reject({
|
|
item: this,
|
|
type: 'error',
|
|
errors: errors || [],
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (response.data.hasOwnProperty('pullTag'))
|
|
{
|
|
if (!this.isTransforming)
|
|
{
|
|
this.registerTransformationHandler(response.data.pullTag);
|
|
}
|
|
this.isTransforming = true;
|
|
}
|
|
else
|
|
{
|
|
this.isTransforming = false;
|
|
if (response.data.data)
|
|
{
|
|
this.width = response.data.data.width;
|
|
this.height = response.data.data.height;
|
|
this.sources = response.data.data.sources;
|
|
}
|
|
|
|
promise.fulfill(this);
|
|
}
|
|
});
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
|
|
handleAfterInit()
|
|
{
|
|
if (this.handleVideoError())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.player.vjsPlayer.videoWidth() > 0 && this.player.vjsPlayer.videoHeight() > 0)
|
|
{
|
|
this.adjustVideo();
|
|
}
|
|
else
|
|
{
|
|
BX.Event.EventEmitter.subscribeOnce(this.player, 'Player:onLoadedMetadata', () => {
|
|
this.adjustVideo();
|
|
});
|
|
}
|
|
},
|
|
|
|
handleVideoError()
|
|
{
|
|
if (this.player.vjsPlayer.error() && !this.forceTransformation)
|
|
{
|
|
this.controller.reload(this, {
|
|
forceTransformation: true,
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
adjustVideo()
|
|
{
|
|
const container = this.contentNode;
|
|
if (!container)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!this.player.vjsPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.adjustVideoWidth(
|
|
this.player.width,
|
|
this.player.height,
|
|
this.player.vjsPlayer.videoWidth(),
|
|
this.player.vjsPlayer.videoHeight(),
|
|
);
|
|
|
|
BX.addClass(container, 'player-loaded');
|
|
BX.style(container, 'opacity', 1);
|
|
|
|
this.controller.hideLoading();
|
|
},
|
|
|
|
adjustVideoWidth(maxWidth, maxHeight, videoWidth, videoHeight)
|
|
{
|
|
if (!maxWidth || !maxHeight || !videoWidth || !videoHeight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
maxHeight = Math.min(maxHeight, window.innerHeight - 250);
|
|
|
|
let width = Math.max(videoWidth, 400);
|
|
let height = Math.max(videoHeight, 130);
|
|
if (videoHeight > maxHeight || videoWidth > maxWidth)
|
|
{
|
|
const resultRelativeSize = maxWidth / maxHeight;
|
|
const videoRelativeSize = videoWidth / videoHeight;
|
|
let reduceRatio = 1;
|
|
if (resultRelativeSize > videoRelativeSize)
|
|
{
|
|
reduceRatio = maxHeight / videoHeight;
|
|
}
|
|
else
|
|
{
|
|
reduceRatio = maxWidth / videoWidth;
|
|
}
|
|
|
|
width = Math.max(videoWidth * reduceRatio, 400);
|
|
height = Math.max(videoHeight * reduceRatio, 130);
|
|
}
|
|
|
|
this.player.vjsPlayer.fluid(false);
|
|
|
|
BX.Dom.style(this.contentNode, 'width', `${width}px`);
|
|
BX.Dom.style(this.contentNode, 'height', `${height}px`);
|
|
|
|
this.player.vjsPlayer.width('auto');
|
|
this.player.vjsPlayer.height('auto');
|
|
|
|
BX.Dom.style(this.player.vjsPlayer.el(), 'width', '100%');
|
|
BX.Dom.style(this.player.vjsPlayer.el(), 'min-width', '300px');
|
|
BX.Dom.style(this.player.vjsPlayer.el(), 'aspect-ratio', `${width} / ${height}`);
|
|
BX.Dom.style(this.player.vjsPlayer.el(), 'height', 'auto');
|
|
|
|
this.videoWidth = width;
|
|
if (!this.contentWidthPromise.state)
|
|
{
|
|
this.contentWidthPromise.fulfill(this.videoWidth);
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* @returns {BX.Promise}
|
|
*/
|
|
getContentWidth()
|
|
{
|
|
this.contentWidthPromise = new BX.Promise();
|
|
|
|
if (this.videoWidth)
|
|
{
|
|
this.contentWidthPromise.fulfill(this.videoWidth);
|
|
}
|
|
|
|
return this.contentWidthPromise;
|
|
},
|
|
|
|
render(options)
|
|
{
|
|
if (this.player === null)
|
|
{
|
|
this.player = new BX.UI.VideoPlayer.Player(this.playerId, {
|
|
autoplay: options.asFirstToShow === true,
|
|
width: this.width,
|
|
height: this.height,
|
|
sources: this.sources,
|
|
disablePictureInPicture: this.shouldDisablePictureInPicture(),
|
|
});
|
|
|
|
this.controller.showLoading();
|
|
|
|
BX.Event.EventEmitter.subscribe(this.player, 'Player:onAfterInit', this.handleAfterInit.bind(this));
|
|
BX.Event.EventEmitter.subscribe(this.player, 'Player:onError', this.handleVideoError.bind(this));
|
|
BX.Event.EventEmitter.subscribe(this.player, 'Player:onEnterPictureInPicture', () => {
|
|
this.controller.close();
|
|
});
|
|
|
|
BX.Event.EventEmitter.subscribe(this.player, 'Player:onLeavePictureInPicture', () => {
|
|
if (this.player)
|
|
{
|
|
this.player.pause();
|
|
if (!this.controller.isOpen())
|
|
{
|
|
this.player.destroy();
|
|
this.player = null;
|
|
}
|
|
}
|
|
});
|
|
|
|
this.contentNode = BX.create('div', {
|
|
attrs: {
|
|
className: 'ui-viewer-video',
|
|
},
|
|
style: {
|
|
opacity: 0,
|
|
},
|
|
children: [
|
|
this.player.createElement(),
|
|
],
|
|
});
|
|
}
|
|
else
|
|
{
|
|
this.adjustVideo();
|
|
}
|
|
|
|
return this.contentNode;
|
|
},
|
|
|
|
shouldDisablePictureInPicture()
|
|
{
|
|
return BX.Browser.isFirefox() || navigator.userAgent.includes('YaBrowser');
|
|
},
|
|
|
|
asFirstToShow()
|
|
{
|
|
if (this.player)
|
|
{
|
|
this.player.vjsPlayer.one('canplay', () => {
|
|
this.player.mute(false);
|
|
this.player.play();
|
|
this.player.focus();
|
|
});
|
|
}
|
|
},
|
|
|
|
afterRender()
|
|
{
|
|
const disablePictureInPicture = this.shouldDisablePictureInPicture() && !this.player.isInited();
|
|
|
|
this.player.init();
|
|
|
|
if (disablePictureInPicture)
|
|
{
|
|
this.player.vjsPlayer.controlBar.removeChild('PictureInPictureToggle');
|
|
}
|
|
},
|
|
|
|
destroy()
|
|
{
|
|
BX.UI.Viewer.Item.prototype.destroy.apply(this);
|
|
|
|
if (this.player !== null && this.player.vjsPlayer && !this.player.vjsPlayer.isInPictureInPicture())
|
|
{
|
|
this.player.destroy();
|
|
this.player = null;
|
|
}
|
|
},
|
|
|
|
beforeHide()
|
|
{
|
|
if (this.player !== null && this.player.vjsPlayer && !this.player.vjsPlayer.isInPictureInPicture())
|
|
{
|
|
this.player.pause();
|
|
}
|
|
},
|
|
|
|
handleResize()
|
|
{
|
|
this.adjustVideo();
|
|
},
|
|
};
|
|
})();
|