mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 01:35:34 +08:00
Merge pull request #2964 from biosistemika/features/office-file-previews
Features/office file previews merge branch
This commit is contained in:
commit
483ff8f1f2
72 changed files with 1928 additions and 1390 deletions
|
@ -1,3 +1,10 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#595959" d="M15.875 6l8.875 4.438v7L23 15.625v-4.063l-7.125-3.563-7.125 3.563v9.375l5.625 2.813-.75 1.563L7 22V10.437L15.875 6zm-.813 4l.875-.5 6.125 3.063v.938l-7-3.501zm-4.5 10.437l-.938-.438v-7.563l.938-.438v8.439zm17.875 1.938c.459 0 .844.156 1.156.469.312.313.468.698.469 1.156v2.375c0 .209-.104.313-.313.313-.209 0-.313-.104-.313-.313V24c0-.25-.104-.48-.313-.688-.209-.209-.438-.313-.688-.313h-1v1c0 .25-.115.375-.344.375-.23 0-.344-.125-.344-.375v-1.313a.974.974 0 0 0-.281-.719.974.974 0 0 0-.719-.281h-1v1.688c0 .209-.115.313-.344.313-.23 0-.344-.104-.344-.313v-1.375c0-.25-.094-.48-.281-.688a.923.923 0 0 0-.719-.313h-1v3c0 .25-.104.375-.313.375-.209 0-.313-.125-.313-.375v-6c0-.25-.104-.48-.313-.688a.989.989 0 0 0-.719-.313c-.27 0-.5.104-.688.313a1.002 1.002 0 0 0-.281.688v8.375c0 .209-.115.313-.344.313-.23 0-.344-.104-.344-.313V25.31l-.938-.438c-.417-.209-.833-.24-1.25-.094-.417.146-.73.427-.938.844l-.188.25c-.083.209-.02.355.188.438.375.209.802.459 1.281.75.479.291 1.083.781 1.813 1.469.73.688 1.115 1.323 1.156 1.906.042.25-.041.375-.25.375h-.063c-.209 0-.313-.083-.313-.25-.083-.625-.563-1.282-1.438-1.969s-1.709-1.24-2.5-1.656a.95.95 0 0 1-.5-.594 1.015 1.015 0 0 1 .063-.781l.125-.25c.292-.583.74-.98 1.344-1.188a2.215 2.215 0 0 1 1.781.125l.625.313v-6.563c0-.459.167-.844.5-1.156.333-.312.73-.468 1.188-.469.459 0 .844.156 1.156.469.312.313.468.699.469 1.156v2.375h1c.583 0 1.042.208 1.375.625h1.313c.417 0 .771.125 1.063.375.292.25.48.583.563 1h1.063l.005.003z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path fill="#404048" fill-rule="evenodd" d="M7.995 1.732L3 4.616v5.768l3.297 1.903c.327.189.462.59.317.938-.174.418-.675.588-1.067.362L2.25 11.683c-.464-.268-.75-.763-.75-1.299V4.616c0-.536.286-1.031.75-1.299L7.245.433c.464-.268 1.036-.268 1.5 0l4.995 2.884c.464.268.75.763.75 1.299v2.886c0 .483-.569.742-.933.423l-.375-.328c-.122-.107-.192-.261-.192-.423V4.616L7.995 1.732zM4.5 4.5l-.75.375v5.25l.75.375v-6zm7.909.654l-.05.837-5.197-3 .7-.462 4.547 2.625zM10.875 8.25c.207 0 .375.168.375.375v3.75c0 .207.168.375.375.375s.375-.168.375-.375V12h.3c.248 0 .45.201.45.45v.675c0 .207.168.375.375.375s.375-.168.375-.375v-.375h.3c.248 0 .45.201.45.45v.675c0 .207.168.375.375.375s.375-.168.375-.375V13.5c.414 0 .75.336.75.75v1.875c0 .207.168.375.375.375s.375-.168.375-.375V14.25c0-.828-.672-1.5-1.5-1.5h-.087c-.178-.44-.61-.75-1.113-.75h-.387c-.178-.44-.61-.75-1.113-.75H12V8.625c0-.621-.504-1.125-1.125-1.125S9.75 8.004 9.75 8.625v4.655c-1.079-.582-2.334.23-2.59 1.086-.05.173-.043.528.173.638.871.442 1.265.756 1.71 1.123.487.4.663.857.707 1.487.015.206.18.375.387.375.207 0 .377-.169.363-.375-.05-.736-.256-1.063-.55-1.488-.313-.451-1-.974-1.904-1.426-.135-.068-.207-.227-.124-.353.297-.458.964-.736 1.512-.384l.316.19v.472c0 .207.168.375.375.375s.375-.168.375-.375v-6c0-.207.168-.375.375-.375z" clip-rule="evenodd"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<path fill="#fff" d="M0 0H18V18H0z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -291,3 +291,10 @@ var HelperModule = (function(){
|
|||
function notTurbolinksPreview() {
|
||||
return !document.documentElement.hasAttribute("data-turbolinks-preview");
|
||||
}
|
||||
|
||||
const windowScrollEvents = {};
|
||||
$(window).scroll(function() {
|
||||
$.each(windowScrollEvents, function(key, scroll_function){
|
||||
scroll_function();
|
||||
})
|
||||
})
|
||||
|
|
|
@ -186,7 +186,6 @@ var MyModuleRepositories = (function() {
|
|||
|
||||
drawCallback: function() {
|
||||
FULL_VIEW_TABLE.columns.adjust();
|
||||
FilePreviewModal.init();
|
||||
renderFullViewRepositoryName(
|
||||
tableContainer.attr('data-repository-name'),
|
||||
tableContainer.attr('data-repository-snapshot-created'),
|
||||
|
|
|
@ -118,7 +118,6 @@
|
|||
ResultAssets.applyEditResultAssetCallback();
|
||||
applyCreateWopiFileCallback();
|
||||
toggleResultEditButtons(true);
|
||||
FilePreviewModal.init();
|
||||
Comments.init();
|
||||
ResultAssets.initNewResultAsset();
|
||||
expandResult($(this));
|
||||
|
|
|
@ -223,7 +223,6 @@ function initProtocolPreviewModal() {
|
|||
modal.modal("show");
|
||||
ProtocolRepositoryHeader.init();
|
||||
initHandsOnTable(modalBody);
|
||||
FilePreviewModal.init({ readOnly: true });
|
||||
},
|
||||
error: function (error) {
|
||||
// TODO
|
||||
|
|
|
@ -69,7 +69,6 @@
|
|||
|
||||
setTimeout(function() {
|
||||
initStepsComments();
|
||||
FilePreviewModal.init();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
TinyMCE.destroyAll();
|
||||
DragNDropSteps.clearFiles();
|
||||
|
@ -97,7 +96,6 @@
|
|||
toggleButtons(false);
|
||||
initializeCheckboxSorting();
|
||||
animateSpinner(null, false);
|
||||
FilePreviewModal.init();
|
||||
DragNDropSteps.clearFiles();
|
||||
if (tinyMCE.editors.step_description_textarea) tinyMCE.editors.step_description_textarea.remove();
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
|
@ -176,8 +174,6 @@
|
|||
initCallBacks();
|
||||
initHandsOnTable($new_step);
|
||||
toggleButtons(true);
|
||||
FilePreviewModal.init();
|
||||
|
||||
TinyMCE.destroyAll();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
// Show the edited step
|
||||
|
@ -553,7 +549,7 @@
|
|||
expandStep($new_step);
|
||||
toggleButtons(true);
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
|
||||
$new_step.find('.attachments').trigger('reorder');
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
|
||||
|
||||
|
@ -563,7 +559,6 @@
|
|||
});
|
||||
animateSpinner(null, false);
|
||||
DragNDropSteps.clearFiles();
|
||||
FilePreviewModal.init();
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
},
|
||||
error: function(xhr) {
|
||||
|
@ -617,28 +612,10 @@
|
|||
Comments.init();
|
||||
}
|
||||
|
||||
// Reorder attachments
|
||||
global.reorderAttachments = function reorderAtt(elem, stepId, sortType) {
|
||||
var label_value = $("#dd-att-step-" + stepId + "> .dropdown-menu > li > a[data-order=" + sortType + "]").html();
|
||||
$("#dd-att-step-" + stepId + "-label").html(label_value);
|
||||
$('#att-' + stepId + ' a.file-preview-link').each(function(){
|
||||
var elm = $(this)
|
||||
elm.parent().css('order', elm.attr('data-order-' + sortType));
|
||||
});
|
||||
|
||||
$.post(
|
||||
$(elem).closest('.dropdown-menu').data('stateSavePath'),
|
||||
{ assets: { order: sortType } },
|
||||
null,
|
||||
'json',
|
||||
);
|
||||
}
|
||||
|
||||
// On init
|
||||
initCallBacks();
|
||||
initHandsOnTable($(document));
|
||||
expandAllSteps();
|
||||
FilePreviewModal.init();
|
||||
TinyMCE.highlight();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
newStepHandler();
|
||||
|
@ -652,3 +629,66 @@
|
|||
|
||||
global.initHandsOnTable = initHandsOnTable;
|
||||
})(window);
|
||||
|
||||
(function() {
|
||||
// Reorder attachments
|
||||
function reorderAttachmentsInit() {
|
||||
$('#steps').on('click', '.attachments-actions .change-order', function(e){
|
||||
var orderDropdown = $(this).closest('.dropdown-menu');
|
||||
var assetsContainer = $(`.attachments[data-step-id=${orderDropdown.data('step-id')}]`)
|
||||
var order = $(this).data('order');
|
||||
e.preventDefault();
|
||||
assetsContainer.data('order', order);
|
||||
orderDropdown.find('.change-order').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
assetsContainer.trigger('reorder');
|
||||
$.post(orderDropdown.data('state-save-url'), {
|
||||
assets: { order: order }
|
||||
});
|
||||
})
|
||||
|
||||
$('#steps').on('reorder', '.attachments', function() {
|
||||
var assets = $(`.attachments[data-step-id=${$(this).data('step-id')}] .asset`);
|
||||
var order = $(this).data('order');
|
||||
var sortedAssets = assets.sort(function(a, b) {
|
||||
if (a.dataset.assetOrder == b.dataset.assetOrder) {
|
||||
if (order == 'new') {
|
||||
return b.dataset.assetUpdatedAt - a.dataset.assetUpdatedAt;
|
||||
} if (order == 'old') {
|
||||
return a.dataset.assetUpdatedAt - b.dataset.assetUpdatedAt;
|
||||
} if (order == 'atoz') {
|
||||
return a.dataset.assetFileName.toLowerCase() > b.dataset.assetFileName.toLowerCase() ? 1 : -1;
|
||||
} if (order == 'ztoa') {
|
||||
return b.dataset.assetFileName.toLowerCase() > a.dataset.assetFileName.toLowerCase() ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return a.dataset.assetOrder > b.dataset.assetOrder ? 1 : -1
|
||||
})
|
||||
|
||||
$.each(sortedAssets, function(i, element){
|
||||
element.style.order = i
|
||||
})
|
||||
})
|
||||
.on('DOMSubtreeModified', '.attachments', function() {
|
||||
$(this).trigger('reorder');
|
||||
})
|
||||
$('.attachments').trigger('reorder');
|
||||
}
|
||||
|
||||
function initAssetViewModeToggle(){
|
||||
$('#steps').on('click', '.attachments-actions .attachments-view-mode', function () {
|
||||
var viewModeBtn = $(this);
|
||||
$.post(viewModeBtn.closest('.dropdown-menu').data('view-mode-url'), {
|
||||
assets_view_mode: viewModeBtn.data('assets-view-mode')
|
||||
}, function(result) {
|
||||
viewModeBtn.closest('.dropdown-menu').find('.attachments-view-mode').removeClass('selected');
|
||||
viewModeBtn.addClass('selected');
|
||||
viewModeBtn.closest('.step').find('.attachments').html(result.html)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
reorderAttachmentsInit();
|
||||
initAssetViewModeToggle();
|
||||
})();
|
||||
|
|
|
@ -118,7 +118,6 @@ var RepositoryDatatable = (function(global) {
|
|||
currentMode = 'viewMode';
|
||||
// Table specific stuff
|
||||
TABLE.button(0).enable(true);
|
||||
FilePreviewModal.init();
|
||||
$(TABLE_WRAPPER_ID).find('tr').removeClass('blocked');
|
||||
updateButtons();
|
||||
disableCheckboxToggleOnCheckboxPreview();
|
||||
|
@ -362,12 +361,6 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
}
|
||||
|
||||
function disableCheckboxToggleOnAssetDownload() {
|
||||
$('.file-preview-link').on('click', function(ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
// Adjust columns width in table header
|
||||
function adjustTableHeader() {
|
||||
TABLE.columns.adjust();
|
||||
|
@ -502,7 +495,6 @@ var RepositoryDatatable = (function(global) {
|
|||
animateSpinner(this, false);
|
||||
changeToViewMode();
|
||||
updateDataTableSelectAllCtrl();
|
||||
FilePreviewModal.init();
|
||||
|
||||
// Prevent row toggling when selecting user smart annotation link
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
|
@ -548,8 +540,6 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
},
|
||||
fnInitComplete: function() {
|
||||
disableCheckboxToggleOnAssetDownload();
|
||||
FilePreviewModal.init();
|
||||
initHeaderTooltip();
|
||||
disableCheckboxToggleOnCheckboxPreview();
|
||||
|
||||
|
|
|
@ -50,7 +50,6 @@
|
|||
$form.remove();
|
||||
applyEditResultAssetCallback();
|
||||
Results.toggleResultEditButtons(true);
|
||||
FilePreviewModal.init();
|
||||
});
|
||||
|
||||
Results.toggleResultEditButtons(false);
|
||||
|
@ -71,7 +70,6 @@
|
|||
|
||||
Results.toggleResultEditButtons(true);
|
||||
Results.expandResult($newResult);
|
||||
FilePreviewModal.init();
|
||||
Comments.init();
|
||||
initNewResultAsset();
|
||||
}).on('ajax:error', function(e, xhr) {
|
||||
|
@ -97,5 +95,4 @@
|
|||
|
||||
ResultAssets.initNewResultAsset();
|
||||
ResultAssets.applyEditResultAssetCallback();
|
||||
FilePreviewModal.init();
|
||||
}(window));
|
||||
|
|
97
app/assets/javascripts/sitewide/assets.js
Normal file
97
app/assets/javascripts/sitewide/assets.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
/* global windowScrollEvents HelperModule I18n */
|
||||
$(document).on('click', '.asset-context-menu .change-preview-type', function(e) {
|
||||
var viewModeBtn = $(this);
|
||||
var viewMode = viewModeBtn.data('preview-type');
|
||||
var toggleUrl = viewModeBtn.closest('.dropdown-menu').data('toggle-view-url');
|
||||
var assetId = viewModeBtn.closest('.dropdown-menu').data('asset-id');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$.ajax({
|
||||
url: toggleUrl,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { asset: { view_mode: viewMode } },
|
||||
success: function(data) {
|
||||
viewModeBtn.closest('.dropdown-menu').find('.change-preview-type').removeClass('selected');
|
||||
viewModeBtn.addClass('selected');
|
||||
$(`.asset[data-asset-id=${assetId}]`).replaceWith(data.html);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.asset .delete-asset', function(e) {
|
||||
var asset = $(this).closest('.asset');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$.ajax({
|
||||
url: $(this).attr('href'),
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
success: function(result) {
|
||||
asset.remove();
|
||||
HelperModule.flashAlertMsg(result.flash, 'success');
|
||||
},
|
||||
error: function() {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var InlineAttachments = (function() {
|
||||
function elementVisible(element) {
|
||||
var elementRect = element.getBoundingClientRect();
|
||||
var elementHeight = $(element).height();
|
||||
return elementRect.top + (elementHeight / 2) >= 0
|
||||
&& elementRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + (elementHeight / 2);
|
||||
}
|
||||
|
||||
function showElement(element) {
|
||||
setTimeout(() => {
|
||||
var iframeUrl = $(element).find('.iframe-placeholder').data('iframe-url');
|
||||
if (elementVisible(element) && iframeUrl) {
|
||||
$(element).find('.iframe-placeholder')
|
||||
.replaceWith(`<iframe class="active-iframe-preview" src="${iframeUrl}"></iframe>`);
|
||||
$(element).addClass('active').attr('data-created-at', new Date().getTime());
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function hideElement(element) {
|
||||
var iframeUrl = $(element).find('.active-iframe-preview').attr('src');
|
||||
if (!elementVisible(element) && iframeUrl) {
|
||||
$(element).find('iframe')
|
||||
.replaceWith(`<div class="iframe-placeholder" data-iframe-url="${iframeUrl}"></div>`);
|
||||
$(element).removeClass('active').attr('data-created-at', null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkForAttachmentsState() {
|
||||
$.each($('.inline-attachment-container'), function(i, element) {
|
||||
showElement(element);
|
||||
});
|
||||
if ($('.active-iframe-preview').length > 5) {
|
||||
let sortedIframes = $('.inline-attachment-container.active').sort(function(a, b) {
|
||||
return +a.dataset.createdAt - +b.dataset.createdAt;
|
||||
});
|
||||
$.each(sortedIframes, function(i, element) {
|
||||
if (hideElement(element)) return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
windowScrollEvents.InlineAttachments = InlineAttachments.scrollEvent;
|
||||
},
|
||||
scrollEvent: () => {
|
||||
checkForAttachmentsState();
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
InlineAttachments.init();
|
||||
InlineAttachments.scrollEvent();
|
||||
});
|
|
@ -288,14 +288,14 @@
|
|||
}
|
||||
|
||||
function uploadedAssetPreview(asset, i) {
|
||||
var html = `<div class="attachment-placeholder pull-left new">
|
||||
<div class="attachment-thumbnail no-shadow new %>">
|
||||
var html = `<div class="attachment-container pull-left new">
|
||||
<div class="attachment-preview no-shadow new %>">
|
||||
<i class="fas fa-image"></i>
|
||||
</div>
|
||||
<div class="attachment-label">
|
||||
${truncateLongString(asset.name, GLOBAL_CONSTANTS.FILENAME_TRUNCATION_LENGTH)}
|
||||
</div>
|
||||
<div class="spencer-bonnet-modif"></div>
|
||||
<div class="attachment-metadata"></div>
|
||||
<div class="remove-icon pull-right">
|
||||
<a data-item-id="${i}" href="#">
|
||||
<span class="fas fa-trash"></span>
|
||||
|
@ -324,7 +324,7 @@
|
|||
function listItems() {
|
||||
totalSize = 0;
|
||||
enableSubmitButton();
|
||||
$('.attachment-placeholder.new').remove();
|
||||
$('.attachment-container.new').remove();
|
||||
dragNdropAssetsOff();
|
||||
|
||||
for (let i = 0; i < droppedFiles.length; i += 1) {
|
||||
|
@ -435,7 +435,6 @@
|
|||
initFormSubmitLinks($(this));
|
||||
ResultAssets.applyEditResultAssetCallback();
|
||||
Results.toggleResultEditButtons(true);
|
||||
FilePreviewModal.init();
|
||||
Comments.init();
|
||||
ResultAssets.initNewResultAsset();
|
||||
Results.expandResult($(this));
|
||||
|
|
|
@ -1,545 +1,36 @@
|
|||
/* eslint no-underscore-dangle: ["error", { "allowAfterThis": true }]*/
|
||||
/* eslint no-use-before-define: ["error", { "functions": false }]*/
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* global Uint8Array fabric tui animateSpinner Assets ActiveStoragePreviews
|
||||
PerfectScrollbar MarvinJsEditor refreshProtocolStatusBar */
|
||||
|
||||
|
||||
var FilePreviewModal = (function() {
|
||||
'use strict';
|
||||
|
||||
var readOnly = false;
|
||||
|
||||
function initPreviewModal(options = {}) {
|
||||
var name;
|
||||
var url;
|
||||
var downloadUrl;
|
||||
readOnly = options.readOnly;
|
||||
|
||||
$('.file-preview-link').off('click');
|
||||
$('.file-preview-link').click(function(e) {
|
||||
e.preventDefault();
|
||||
name = $(this).find('.attachment-label').text();
|
||||
url = $(this).data('preview-url');
|
||||
downloadUrl = $(this).attr('href');
|
||||
openPreviewModal(name, url, downloadUrl);
|
||||
return true;
|
||||
});
|
||||
|
||||
$('#filePreviewModal').find('.preview-close').click(function() {
|
||||
$('#filePreviewModal').find('.file-preview-container').html('');
|
||||
$('#filePreviewModal').modal('hide');
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
});
|
||||
}
|
||||
|
||||
// Adding rotation icon
|
||||
function updateFabricControls() {
|
||||
fabric.Object.prototype.drawBorders = function(ctx, styleOverride = {}) {
|
||||
var wh = this._calculateCurrentDimensions();
|
||||
var strokeWidth = 1 / this.borderScaleFactor;
|
||||
var width = wh.x + strokeWidth;
|
||||
var height = wh.y + strokeWidth;
|
||||
var drawRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined'
|
||||
? styleOverride.hasRotatingPoint : this.hasRotatingPoint;
|
||||
var hasControls = typeof styleOverride.hasControls !== 'undefined'
|
||||
? styleOverride.hasControls : this.hasControls;
|
||||
var rotatingPointOffset = typeof styleOverride.rotatingPointOffset !== 'undefined'
|
||||
? styleOverride.rotatingPointOffset : this.rotatingPointOffset;
|
||||
var rotateHeight = -height / 2;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
|
||||
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
|
||||
ctx.strokeRect(
|
||||
-width / 2,
|
||||
-height / 2,
|
||||
width,
|
||||
height
|
||||
);
|
||||
if (drawRotatingPoint && this.isControlVisible('mtr') && hasControls) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, rotateHeight);
|
||||
ctx.lineTo(0, rotateHeight - rotatingPointOffset + 10);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
return this;
|
||||
};
|
||||
|
||||
fabric.Object.prototype.drawControls = function(ctx) {
|
||||
var rotationImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHEklEQVRoge2bYYgdVxXHf/8lhGUJYQ1hKSFIiDXUEoq+e1lClLgfRLSGfNGSVvxgVIyiIqGWEhfpp6IBayiSBJQiQo3YKEQqsQSpmkopYWYJIUSIEkrYhhiWEEIoyxLe3w8zb53Mm/fezNvdlhf7h8feeTv3zvnPPffcc885T8b8P2Hs/RbgvcYHhB90rFurgWOIY8A4sNN2S9JHbG+VtBEYt70E3AFuSvoXcDH/3E7SpL1WcqnKaMUQ1wFjSZosNRkshrgR2A60gKdsR0mTALaRxID2InBR0ingHHA1SZOFIblVootwDHEn8ITtDZJ+naTJxUGDxBAngN2290uaAbaxMu1p274u6S3gN8DfkjS5vYLxltFFOLTCG5KmyQT+h+1n0rn0fFXnXG132n4mJ7qFVbYLtm9KOg8cJSO+InW/j3AMcQr4T+meS8DTSZqc7XwRWmFM0hbbX5U0S7ZW1xpt4DhwwvaVdC69N8wg5dmoUsOdwNEY4nTnC0m7gBNNyNpeaXsM+BbwS0n76jyzCuUZ3gK8UxY0NyrnbT8taQx4yfb2vP1+4AbwLHAySZNGMz1Q4I4FBaYl/QH4M/BwsW+dWVplPAT8HPhhaIWNTTo2taRTnUbhRdRpt4EFYD7/e8/2OmBS0pacQFNZNgKHJC0BP6nbaaBKrwCLwHXgNeBV4Jrtd4EloJ2/jPXABBnhL9h+XNJ2mhnBO7YPSDpdx4J3Ebb9jqTltdvLSRggwAXgmKTXkjS5U1fyGOI4sAf4PjANbK7Tz/ZVSV8Hzg0i3aVGHULlv+V2nwcfk/RKkibzdYQtIkmTReBsDDEB9gHfAeIgL03SNuAQ8G+yZdMTPY1WQ4PTBi5LegI4PgzZIpI0uQW8DOwHzkjqa4ltjwGfBb40aOyh1nDFW75r+2A6l54c1LcpYoibbf8I+BqwobPMAIrt/PqW7U+kc+m1XuPV2kfLW02Fmm+QNBtDbDWjMxhJmixI+rGkl4vLrNzOrzcBB0Mr9OQ1kHC/9VO8B3gUOBFaYXfuY68akjS5YXsWqPTpS7Luk7Sz1z1dgpXXbsP9dlrSMWCmn2DDIJ1Lb9k+BLxd/l9RxYEdwN78iNuFoYzWAM/q42S+d8+3PCwkXQBO5ufm4vdFOdYDn6TgJBXRRbhqO+p1T5/2Y8ALMcStg0g0QZIm7wK/I9t+llGx1HaTncm7UCa8SLbFDEQN//kR2zvqjNUESZpclHSm+F2F5Z4kI92F+wjbvg0kgx5aM1wzD1xpyKcuftt5XgcVmvbpqo73Lex0Lm3HEA8AB4CHbY/12/f6tBckvUjmS68FLgJXc7+7Fyq3yMog3igghvgSmTPSz8B+KJ1L74uFjXJc+p9Fzav6AB8udxplwm+XnSDb97U7IeIiRpaw7eVjZ/EYWzrSbij3G1nCRTQJK40s4Txl02lT1QbulvuNLGHb22rc05WtGFnCkj5WMlDLRqvgL3Sdi9cse7iWyI+fM/3CUcD18h4MI0qY7HCy7GX18ATnqjqOqko/VbyoMlqS/l7VceQIh1Z4DHi83z2SbgNvVv1vpAjHECck7Sc72PTcc22/RYXBghEjTLZ2nwTGywGKwvWSpDfIEm5dGMpo5Xnk7wEztr8t6fJa1mXkz9xk+0VJ23u4kR1cAc70yio2nuG8vOE54AfAp/Kg3aNDcGjyzIeA5/PKhL7ZENt/IjsvV6IR4RjiBtuHbX+D/yW8dtk+Elph1cM5+TM3A4dtfwWq/ebCer4l6UQ/basdAMgfPAt8E5gohXPaki6TRUou5TmiFSEPs24lywN/jsHLbxGYTdLkZ/1uqjXDeVbvObKSgwnoUqsxstKIU7a/G1qh6+DdBKEVNtn+MnAK2Mtgsm3bfwFeGTR2rRkOrfAZSa9SL297F7hANjNnm5QbhVZYL2mP7UP5eq2VLgWuArXSpbUIxxB3AH8lK0uqhbzSbh44C/wRuCZpOSGe39ZJiE8Bnwf22t4OTPTLTZfadyQdAJonxPshhvgkcIQ8TlRTmM7W0Za0YHs+z/AtkS2nSWXlT1vI1bZXlLRH+7akI0ma1C55aLIPn3ZWnfe87am6ZHMBx4ApSVMdgYvod92nfYesWO14Aw7NwrT5sWyP7V+RzXTtba3JCxqUsZR0A3jW9smmBWpDxaVjiDPAYTLy44MEXS3YvicpAV5I0uT3w4wxlC9t+xxwEPipCuUI/TygvN9K2m1JvyCzxqeHkRtWmHnIVXyarKBkD1n50WrP9E3biaSjtl9P59LVKy4dFrnLOaOsqGUPPdZ3gzXbJjvtvEmWOHt9zcqHV4IY4iRZWeIu4ItAy3aT0sBF4JKkU7bPSbqapMnNVROQNUym5eq+EXiErCrgo2QqP0n2E4B7+dZyEyj+BODWe/4TgAcZoxbxWDE+IPyg47+vLtaj5o1LZgAAAABJRU5ErkJggg==';
|
||||
var rotate = new Image();
|
||||
var rotateLeft;
|
||||
var rotateTop;
|
||||
var wh = this._calculateCurrentDimensions();
|
||||
var width = wh.x;
|
||||
var height = wh.y;
|
||||
var scaleOffset = this.cornerSize;
|
||||
var left = -(width + scaleOffset) / 2;
|
||||
var top = -(height + scaleOffset) / 2;
|
||||
var methodName = this.transparentCorners ? 'stroke' : 'fill';
|
||||
|
||||
if (!this.hasControls) {
|
||||
return this;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.strokeStyle = this.cornerColor;
|
||||
ctx.fillStyle = this.cornerColor;
|
||||
if (!this.transparentCorners) {
|
||||
ctx.strokeStyle = this.cornerStrokeColor;
|
||||
}
|
||||
this._setLineDash(ctx, this.cornerDashArray, null);
|
||||
|
||||
// top-left
|
||||
this._drawControl(
|
||||
'tl',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top
|
||||
);
|
||||
|
||||
// top-right
|
||||
this._drawControl(
|
||||
'tr',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top
|
||||
);
|
||||
|
||||
// bottom-left
|
||||
this._drawControl(
|
||||
'bl',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top + height
|
||||
);
|
||||
|
||||
// bottom-right
|
||||
this._drawControl(
|
||||
'br',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top + height
|
||||
);
|
||||
|
||||
if (!this.get('lockUniScaling')) {
|
||||
// middle-top
|
||||
this._drawControl(
|
||||
'mt',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width / 2,
|
||||
top
|
||||
);
|
||||
|
||||
// middle-bottom
|
||||
this._drawControl(
|
||||
'mb',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width / 2,
|
||||
top + height
|
||||
);
|
||||
|
||||
// middle-right
|
||||
this._drawControl(
|
||||
'mr',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top + height / 2
|
||||
);
|
||||
|
||||
// middle-left
|
||||
this._drawControl(
|
||||
'ml',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top + height / 2
|
||||
);
|
||||
}
|
||||
// middle-top-rotate
|
||||
if (this.hasRotatingPoint) {
|
||||
rotate.src = rotationImage;
|
||||
rotateLeft = left + width / 2 - 6;
|
||||
rotateTop = top - this.rotatingPointOffset - 6;
|
||||
ctx.drawImage(rotate, rotateLeft, rotateTop, 32, 32);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
function preInitImageEditor(data) {
|
||||
$.ajax({
|
||||
url: data['download-url'],
|
||||
type: 'get',
|
||||
success: function(responseData) {
|
||||
var fileUrl = responseData;
|
||||
initImageEditor(data, fileUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initImageEditor(data, fileUrl) {
|
||||
var imageEditor;
|
||||
var ps;
|
||||
var blackTheme = {
|
||||
'common.bi.image': '',
|
||||
'common.bisize.width': '0',
|
||||
'common.bisize.height': '0',
|
||||
'common.backgroundImage': 'none',
|
||||
'common.backgroundColor': '#1e1e1e',
|
||||
'common.border': '0px',
|
||||
|
||||
// header
|
||||
'header.backgroundImage': 'none',
|
||||
'header.backgroundColor': 'transparent',
|
||||
'header.border': '0px',
|
||||
|
||||
// load button
|
||||
'loadButton.backgroundColor': '#fff',
|
||||
'loadButton.border': '1px solid #ddd',
|
||||
'loadButton.color': '#222',
|
||||
'loadButton.fontFamily': '\'Noto Sans\', sans-serif',
|
||||
'loadButton.fontSize': '12px',
|
||||
|
||||
// download button
|
||||
'downloadButton.backgroundColor': '#fdba3b',
|
||||
'downloadButton.border': '1px solid #fdba3b',
|
||||
'downloadButton.color': '#fff',
|
||||
'downloadButton.fontFamily': '\'Noto Sans\', sans-serif',
|
||||
'downloadButton.fontSize': '12px',
|
||||
|
||||
// main icons
|
||||
'menu.normalIcon.path': '/images/icon-d.svg',
|
||||
'menu.normalIcon.name': 'icon-d',
|
||||
'menu.activeIcon.path': '/images/icon-b.svg',
|
||||
'menu.activeIcon.name': 'icon-b',
|
||||
'menu.disabledIcon.path': '/images/icon-a.svg',
|
||||
'menu.disabledIcon.name': 'icon-a',
|
||||
'menu.hoverIcon.path': '/images/icon-c.svg',
|
||||
'menu.hoverIcon.name': 'icon-c',
|
||||
'menu.iconSize.width': '24px',
|
||||
'menu.iconSize.height': '24px',
|
||||
|
||||
// submenu primary color
|
||||
'submenu.backgroundColor': '#1e1e1e',
|
||||
'submenu.partition.color': '#3c3c3c',
|
||||
|
||||
// submenu icons
|
||||
'submenu.normalIcon.path': '/images/icon-d.svg',
|
||||
'submenu.normalIcon.name': 'icon-d',
|
||||
'submenu.activeIcon.path': '/images/icon-c.svg',
|
||||
'submenu.activeIcon.name': 'icon-c',
|
||||
'submenu.iconSize.width': '32px',
|
||||
'submenu.iconSize.height': '32px',
|
||||
|
||||
// submenu labels
|
||||
'submenu.normalLabel.color': '#8a8a8a',
|
||||
'submenu.normalLabel.fontWeight': 'lighter',
|
||||
'submenu.activeLabel.color': '#fff',
|
||||
'submenu.activeLabel.fontWeight': 'lighter',
|
||||
|
||||
// checkbox style
|
||||
'checkbox.border': '0px',
|
||||
'checkbox.backgroundColor': '#fff',
|
||||
|
||||
// range style
|
||||
'range.pointer.color': '#fff',
|
||||
'range.bar.color': '#666',
|
||||
'range.subbar.color': '#d1d1d1',
|
||||
|
||||
'range.disabledPointer.color': '#414141',
|
||||
'range.disabledBar.color': '#282828',
|
||||
'range.disabledSubbar.color': '#414141',
|
||||
|
||||
'range.value.color': '#fff',
|
||||
'range.value.fontWeight': 'lighter',
|
||||
'range.value.fontSize': '11px',
|
||||
'range.value.border': '1px solid #353535',
|
||||
'range.value.backgroundColor': '#151515',
|
||||
'range.title.color': '#fff',
|
||||
'range.title.fontWeight': 'lighter',
|
||||
|
||||
// colorpicker style
|
||||
'colorpicker.button.border': '1px solid #1e1e1e',
|
||||
'colorpicker.title.color': '#fff'
|
||||
};
|
||||
|
||||
animateSpinner(null, true);
|
||||
imageEditor = new tui.ImageEditor('#tui-image-editor', {
|
||||
includeUI: {
|
||||
loadImage: {
|
||||
path: fileUrl,
|
||||
name: data.filename
|
||||
},
|
||||
theme: blackTheme,
|
||||
initMenu: 'draw',
|
||||
menuBarPosition: 'bottom'
|
||||
},
|
||||
cssMaxWidth: 700,
|
||||
cssMaxHeight: 500,
|
||||
selectionStyle: {
|
||||
cornerSize: 20,
|
||||
rotatingPointOffset: 70,
|
||||
borderColor: '#333',
|
||||
cornerColor: '#333',
|
||||
cornerStyle: 'circle',
|
||||
borderScaleFactor: 3
|
||||
|
||||
},
|
||||
usageStatistics: false
|
||||
});
|
||||
|
||||
imageEditor.on('image_loaded', () => {
|
||||
$('.file-save-link').css('display', '');
|
||||
animateSpinner(null, false);
|
||||
});
|
||||
|
||||
ps = new PerfectScrollbar($('.tui-image-editor-wrap')[0], { wheelSpeed: 0.5 });
|
||||
$('#tui-image-editor .tui-image-editor').on('mousewheel', (e) => {
|
||||
var imageOriginalSize = {
|
||||
width: imageEditor._graphics.canvasImage.width,
|
||||
height: imageEditor._graphics.canvasImage.height
|
||||
};
|
||||
var wDelta = e.originalEvent.wheelDelta || e.originalEvent.deltaY;
|
||||
var imageEditorWindow = e.currentTarget;
|
||||
var scrollContainer = $('.tui-image-editor-wrap');
|
||||
var initWidth = imageEditorWindow.style.width;
|
||||
var initHeight = imageEditorWindow.style.height;
|
||||
|
||||
var scrollContainerInitial = {
|
||||
top: scrollContainer.scrollTop(),
|
||||
left: scrollContainer.scrollLeft(),
|
||||
height: scrollContainer[0].scrollHeight,
|
||||
width: scrollContainer[0].scrollWidth
|
||||
};
|
||||
|
||||
var mousePosition = {
|
||||
top: e.clientY - (imageEditorWindow.offsetTop - scrollContainerInitial.top),
|
||||
left: e.clientX - $(imageEditorWindow).offset().left
|
||||
};
|
||||
|
||||
|
||||
var newWidth;
|
||||
var newHeight;
|
||||
var offsetY;
|
||||
var offsetX;
|
||||
if (wDelta > 0) {
|
||||
newWidth = parseInt(initWidth, 10) * 1.1;
|
||||
newHeight = parseInt(initHeight, 10) * 1.1;
|
||||
if (newWidth > imageOriginalSize.width || newHeight > imageOriginalSize.height) {
|
||||
newWidth = imageOriginalSize.width;
|
||||
newHeight = imageOriginalSize.height;
|
||||
}
|
||||
} else {
|
||||
newWidth = parseInt(initWidth, 10) * 0.9;
|
||||
newHeight = parseInt(initHeight, 10) * 0.9;
|
||||
if (parseInt(imageEditorWindow.dataset.minWidth, 10) * 0.5 > parseInt(newWidth, 10)) {
|
||||
newWidth = parseInt(imageEditorWindow.dataset.minWidth, 10) * 0.5;
|
||||
newHeight = parseInt(imageEditorWindow.dataset.minHeight, 10) * 0.5;
|
||||
}
|
||||
}
|
||||
imageEditorWindow.style.width = newWidth + 'px';
|
||||
imageEditorWindow.style.height = newHeight + 'px';
|
||||
$(imageEditorWindow).find('canvas, .tui-image-editor-canvas-container')
|
||||
.css('max-width', imageEditorWindow.style.width)
|
||||
.css('max-height', imageEditorWindow.style.height);
|
||||
if (imageEditorWindow.dataset.minHeight === undefined) {
|
||||
imageEditorWindow.dataset.minHeight = initHeight;
|
||||
imageEditorWindow.dataset.minWidth = initWidth;
|
||||
}
|
||||
|
||||
offsetY = (scrollContainer[0].scrollHeight - scrollContainerInitial.height)
|
||||
* (mousePosition.top / scrollContainerInitial.height);
|
||||
offsetX = (scrollContainer[0].scrollWidth - scrollContainerInitial.width)
|
||||
* (mousePosition.left / scrollContainerInitial.width);
|
||||
|
||||
scrollContainer.scrollTop(scrollContainerInitial.top + offsetY);
|
||||
scrollContainer.scrollLeft(scrollContainerInitial.left + offsetX);
|
||||
|
||||
ps.update();
|
||||
|
||||
function initPreviewModal() {
|
||||
$(document).on('click', '.file-preview-link', function(e) {
|
||||
var params = {};
|
||||
var galleryViewId = $(this).data('gallery-view-id');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('.tui-image-editor-wrap')[0].onwheel = function() { return false; };
|
||||
$('.tui-image-editor-wrap').css('height', 'calc(100% - 150px)');
|
||||
|
||||
$('#fileEditModal').find('.file-name').text('Editing: ' + data.filename);
|
||||
$('#fileEditModal').modal('show');
|
||||
|
||||
$('.tui-image-editor-header').hide();
|
||||
|
||||
$('.file-save-link').css('display', 'none');
|
||||
$('.file-save-link').off().click(function(ev) {
|
||||
var imageBlob;
|
||||
var imageDataURL;
|
||||
var imageParams;
|
||||
var dataUpload = new FormData();
|
||||
var blobArray;
|
||||
var bytePosition;
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (data['mime-type'] === 'image/png') {
|
||||
imageParams = { format: 'png' };
|
||||
} else {
|
||||
imageParams = { format: 'jpeg', quality: (data.quality / 100) };
|
||||
}
|
||||
|
||||
imageDataURL = imageEditor.toDataURL(imageParams);
|
||||
imageDataURL = atob(imageDataURL.split(',')[1]);
|
||||
|
||||
blobArray = new Uint8Array(imageDataURL.length);
|
||||
|
||||
for (bytePosition = 0; bytePosition < imageDataURL.length; bytePosition += 1) {
|
||||
blobArray[bytePosition] = imageDataURL.charCodeAt(bytePosition);
|
||||
}
|
||||
|
||||
imageBlob = new Blob([blobArray]);
|
||||
|
||||
function closeEditor() {
|
||||
animateSpinner(null, false);
|
||||
imageEditor.destroy();
|
||||
imageEditor = {};
|
||||
$('#tui-image-editor').html('');
|
||||
$('#fileEditModal').modal('hide');
|
||||
}
|
||||
|
||||
dataUpload.append('image', imageBlob);
|
||||
animateSpinner(null, true);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/files/' + data.id + '/update_image',
|
||||
data: dataUpload,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(res) {
|
||||
$('#modal_link' + data.id).parent().html(res.html);
|
||||
initPreviewModal();
|
||||
}
|
||||
}).done(function() {
|
||||
closeEditor();
|
||||
|
||||
params.gallery = $(`.file-preview-link[data-gallery-view-id=${galleryViewId}]`)
|
||||
.toArray().sort((a, b) => $(a).closest('.asset').css('order') - $(b).closest('.asset').css('order'))
|
||||
.map(i => i.dataset.id);
|
||||
$.get($(this).data('preview-url'), params, function(result) {
|
||||
$('#filePreviewModal .modal-content').html(result.html);
|
||||
$('#filePreviewModal').modal('show');
|
||||
});
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
});
|
||||
|
||||
window.onresize = function() {
|
||||
imageEditor.ui.resizeEditor();
|
||||
};
|
||||
}
|
||||
|
||||
function openPreviewModal(name, url, downloadUrl) {
|
||||
var modal = $('#filePreviewModal');
|
||||
updateFabricControls();
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var link = modal.find('.file-download-link');
|
||||
clearPrevieModal();
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'wopi-controls')) {
|
||||
modal.find('.file-wopi-controls').html(data['wopi-controls']);
|
||||
}
|
||||
link.attr('href', downloadUrl);
|
||||
link.attr('data-no-turbolink', true);
|
||||
link.attr('data-status', 'asset-present');
|
||||
if (data.type === 'previewable') {
|
||||
animateSpinner('.file-preview-container', false);
|
||||
modal.find('.file-preview-container')
|
||||
.append($('<img>')
|
||||
.css('opacity', 0)
|
||||
.attr('src', data['large-preview-url'])
|
||||
.attr('alt', name)
|
||||
.on('error', ActiveStoragePreviews.reCheckPreview)
|
||||
.on('load', ActiveStoragePreviews.showPreview)
|
||||
.click(function(ev) {
|
||||
ev.stopPropagation();
|
||||
}));
|
||||
if (!readOnly && data.editable) {
|
||||
modal.find('.file-edit-link').css('display', '');
|
||||
modal.find('.file-edit-link').off().click(function(ev) {
|
||||
$.post('/files/' + data.id + '/start_edit_image');
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
modal.modal('hide');
|
||||
preInitImageEditor(data);
|
||||
});
|
||||
} else {
|
||||
modal.find('.file-edit-link').css('display', 'none');
|
||||
}
|
||||
} else if (data.type === 'marvinjs') {
|
||||
openMarvinEditModal(data, modal);
|
||||
} else {
|
||||
modal.find('.file-edit-link').css('display', 'none');
|
||||
modal.find('.file-preview-container').html(data['preview-icon']);
|
||||
}
|
||||
if (readOnly) {
|
||||
modal.find('#wopi_file_edit_button').remove();
|
||||
}
|
||||
modal.find('.file-name').text(name);
|
||||
modal.modal();
|
||||
modal.find('a[disabled=disabled]').click(function(ev) {
|
||||
ev.preventDefault();
|
||||
});
|
||||
$('.modal-backdrop').last().css('z-index', modal.css('z-index') - 1);
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearPrevieModal() {
|
||||
var modal = $('#filePreviewModal');
|
||||
modal.find('.file-preview-container').empty();
|
||||
modal.find('.file-wopi-controls').empty();
|
||||
modal.find('.file-edit-link').css('display', 'none');
|
||||
}
|
||||
|
||||
function openMarvinEditModal(data, modal) {
|
||||
modal.find('.file-preview-container')
|
||||
.append($('<img>')
|
||||
.css('opacity', 0)
|
||||
.attr('src', data['large-preview-url'])
|
||||
.attr('alt', data.name)
|
||||
.on('error', ActiveStoragePreviews.reCheckPreview)
|
||||
.on('load', ActiveStoragePreviews.showPreview)
|
||||
.click(function(ev) {
|
||||
ev.stopPropagation();
|
||||
}));
|
||||
if (!readOnly && data.editable) {
|
||||
modal.find('.file-edit-link').css('display', '');
|
||||
modal.find('.file-edit-link').off().click(function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
modal.modal('hide');
|
||||
$.post(data['update-url'] + '/start_editing');
|
||||
MarvinJsEditor.open({
|
||||
mode: 'edit',
|
||||
data: data.description,
|
||||
name: data.name,
|
||||
marvinUrl: data['update-url']
|
||||
});
|
||||
$(document).on('click', '#filePreviewModal .gallery-switcher', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$.get($(this).attr('href'), { gallery: $(this).data('gallery-elements') }, function(result) {
|
||||
$('#filePreviewModal .modal-content').html(result.html);
|
||||
});
|
||||
} else {
|
||||
modal.find('.file-edit-link').css('display', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
init: initPreviewModal,
|
||||
imageEditor: initImageEditor
|
||||
init: initPreviewModal
|
||||
});
|
||||
}(window));
|
||||
}());
|
||||
|
||||
FilePreviewModal.init();
|
||||
|
|
420
app/assets/javascripts/sitewide/image_editor.js
Normal file
420
app/assets/javascripts/sitewide/image_editor.js
Normal file
|
@ -0,0 +1,420 @@
|
|||
/* global animateSpinner fabric PerfectScrollbar refreshProtocolStatusBar tui Uint8Array*/
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
var ImageEditorModal = (function() {
|
||||
function updateFabricControls() {
|
||||
fabric.Object.prototype.drawBorders = function(ctx, styleOverride = {}) {
|
||||
var wh = this._calculateCurrentDimensions();
|
||||
var strokeWidth = 1 / this.borderScaleFactor;
|
||||
var width = wh.x + strokeWidth;
|
||||
var height = wh.y + strokeWidth;
|
||||
var drawRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined'
|
||||
? styleOverride.hasRotatingPoint : this.hasRotatingPoint;
|
||||
var hasControls = typeof styleOverride.hasControls !== 'undefined'
|
||||
? styleOverride.hasControls : this.hasControls;
|
||||
var rotatingPointOffset = typeof styleOverride.rotatingPointOffset !== 'undefined'
|
||||
? styleOverride.rotatingPointOffset : this.rotatingPointOffset;
|
||||
var rotateHeight = -height / 2;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
|
||||
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
|
||||
ctx.strokeRect(
|
||||
-width / 2,
|
||||
-height / 2,
|
||||
width,
|
||||
height
|
||||
);
|
||||
if (drawRotatingPoint && this.isControlVisible('mtr') && hasControls) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, rotateHeight);
|
||||
ctx.lineTo(0, rotateHeight - rotatingPointOffset + 10);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
return this;
|
||||
};
|
||||
|
||||
fabric.Object.prototype.drawControls = function(ctx) {
|
||||
var rotationImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHEklEQVRoge2bYYgdVxXHf/8lhGUJYQ1hKSFIiDXUEoq+e1lClLgfRLSGfNGSVvxgVIyiIqGWEhfpp6IBayiSBJQiQo3YKEQqsQSpmkopYWYJIUSIEkrYhhiWEEIoyxLe3w8zb53Mm/fezNvdlhf7h8feeTv3zvnPPffcc885T8b8P2Hs/RbgvcYHhB90rFurgWOIY8A4sNN2S9JHbG+VtBEYt70E3AFuSvoXcDH/3E7SpL1WcqnKaMUQ1wFjSZosNRkshrgR2A60gKdsR0mTALaRxID2InBR0ingHHA1SZOFIblVootwDHEn8ITtDZJ+naTJxUGDxBAngN2290uaAbaxMu1p274u6S3gN8DfkjS5vYLxltFFOLTCG5KmyQT+h+1n0rn0fFXnXG132n4mJ7qFVbYLtm9KOg8cJSO+InW/j3AMcQr4T+meS8DTSZqc7XwRWmFM0hbbX5U0S7ZW1xpt4DhwwvaVdC69N8wg5dmoUsOdwNEY4nTnC0m7gBNNyNpeaXsM+BbwS0n76jyzCuUZ3gK8UxY0NyrnbT8taQx4yfb2vP1+4AbwLHAySZNGMz1Q4I4FBaYl/QH4M/BwsW+dWVplPAT8HPhhaIWNTTo2taRTnUbhRdRpt4EFYD7/e8/2OmBS0pacQFNZNgKHJC0BP6nbaaBKrwCLwHXgNeBV4Jrtd4EloJ2/jPXABBnhL9h+XNJ2mhnBO7YPSDpdx4J3Ebb9jqTltdvLSRggwAXgmKTXkjS5U1fyGOI4sAf4PjANbK7Tz/ZVSV8Hzg0i3aVGHULlv+V2nwcfk/RKkibzdYQtIkmTReBsDDEB9gHfAeIgL03SNuAQ8G+yZdMTPY1WQ4PTBi5LegI4PgzZIpI0uQW8DOwHzkjqa4ltjwGfBb40aOyh1nDFW75r+2A6l54c1LcpYoibbf8I+BqwobPMAIrt/PqW7U+kc+m1XuPV2kfLW02Fmm+QNBtDbDWjMxhJmixI+rGkl4vLrNzOrzcBB0Mr9OQ1kHC/9VO8B3gUOBFaYXfuY68akjS5YXsWqPTpS7Luk7Sz1z1dgpXXbsP9dlrSMWCmn2DDIJ1Lb9k+BLxd/l9RxYEdwN78iNuFoYzWAM/q42S+d8+3PCwkXQBO5ufm4vdFOdYDn6TgJBXRRbhqO+p1T5/2Y8ALMcStg0g0QZIm7wK/I9t+llGx1HaTncm7UCa8SLbFDEQN//kR2zvqjNUESZpclHSm+F2F5Z4kI92F+wjbvg0kgx5aM1wzD1xpyKcuftt5XgcVmvbpqo73Lex0Lm3HEA8AB4CHbY/12/f6tBckvUjmS68FLgJXc7+7Fyq3yMog3igghvgSmTPSz8B+KJ1L74uFjXJc+p9Fzav6AB8udxplwm+XnSDb97U7IeIiRpaw7eVjZ/EYWzrSbij3G1nCRTQJK40s4Txl02lT1QbulvuNLGHb22rc05WtGFnCkj5WMlDLRqvgL3Sdi9cse7iWyI+fM/3CUcD18h4MI0qY7HCy7GX18ATnqjqOqko/VbyoMlqS/l7VceQIh1Z4DHi83z2SbgNvVv1vpAjHECck7Sc72PTcc22/RYXBghEjTLZ2nwTGywGKwvWSpDfIEm5dGMpo5Xnk7wEztr8t6fJa1mXkz9xk+0VJ23u4kR1cAc70yio2nuG8vOE54AfAp/Kg3aNDcGjyzIeA5/PKhL7ZENt/IjsvV6IR4RjiBtuHbX+D/yW8dtk+Elph1cM5+TM3A4dtfwWq/ebCer4l6UQ/basdAMgfPAt8E5gohXPaki6TRUou5TmiFSEPs24lywN/jsHLbxGYTdLkZ/1uqjXDeVbvObKSgwnoUqsxstKIU7a/G1qh6+DdBKEVNtn+MnAK2Mtgsm3bfwFeGTR2rRkOrfAZSa9SL297F7hANjNnm5QbhVZYL2mP7UP5eq2VLgWuArXSpbUIxxB3AH8lK0uqhbzSbh44C/wRuCZpOSGe39ZJiE8Bnwf22t4OTPTLTZfadyQdAJonxPshhvgkcIQ8TlRTmM7W0Za0YHs+z/AtkS2nSWXlT1vI1bZXlLRH+7akI0ma1C55aLIPn3ZWnfe87am6ZHMBx4ApSVMdgYvod92nfYesWO14Aw7NwrT5sWyP7V+RzXTtba3JCxqUsZR0A3jW9smmBWpDxaVjiDPAYTLy44MEXS3YvicpAV5I0uT3w4wxlC9t+xxwEPipCuUI/TygvN9K2m1JvyCzxqeHkRtWmHnIVXyarKBkD1n50WrP9E3biaSjtl9P59LVKy4dFrnLOaOsqGUPPdZ3gzXbJjvtvEmWOHt9zcqHV4IY4iRZWeIu4ItAy3aT0sBF4JKkU7bPSbqapMnNVROQNUym5eq+EXiErCrgo2QqP0n2E4B7+dZyEyj+BODWe/4TgAcZoxbxWDE+IPyg47+vLtaj5o1LZgAAAABJRU5ErkJggg==';
|
||||
var rotate = new Image();
|
||||
var rotateLeft;
|
||||
var rotateTop;
|
||||
var wh = this._calculateCurrentDimensions();
|
||||
var width = wh.x;
|
||||
var height = wh.y;
|
||||
var scaleOffset = this.cornerSize;
|
||||
var left = -(width + scaleOffset) / 2;
|
||||
var top = -(height + scaleOffset) / 2;
|
||||
var methodName = this.transparentCorners ? 'stroke' : 'fill';
|
||||
|
||||
if (!this.hasControls) {
|
||||
return this;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.strokeStyle = this.cornerColor;
|
||||
ctx.fillStyle = this.cornerColor;
|
||||
if (!this.transparentCorners) {
|
||||
ctx.strokeStyle = this.cornerStrokeColor;
|
||||
}
|
||||
this._setLineDash(ctx, this.cornerDashArray, null);
|
||||
|
||||
// top-left
|
||||
this._drawControl(
|
||||
'tl',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top
|
||||
);
|
||||
|
||||
// top-right
|
||||
this._drawControl(
|
||||
'tr',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top
|
||||
);
|
||||
|
||||
// bottom-left
|
||||
this._drawControl(
|
||||
'bl',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top + height
|
||||
);
|
||||
|
||||
// bottom-right
|
||||
this._drawControl(
|
||||
'br',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top + height
|
||||
);
|
||||
|
||||
if (!this.get('lockUniScaling')) {
|
||||
// middle-top
|
||||
this._drawControl(
|
||||
'mt',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width / 2,
|
||||
top
|
||||
);
|
||||
|
||||
// middle-bottom
|
||||
this._drawControl(
|
||||
'mb',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width / 2,
|
||||
top + height
|
||||
);
|
||||
|
||||
// middle-right
|
||||
this._drawControl(
|
||||
'mr',
|
||||
ctx,
|
||||
methodName,
|
||||
left + width,
|
||||
top + height / 2
|
||||
);
|
||||
|
||||
// middle-left
|
||||
this._drawControl(
|
||||
'ml',
|
||||
ctx,
|
||||
methodName,
|
||||
left,
|
||||
top + height / 2
|
||||
);
|
||||
}
|
||||
// middle-top-rotate
|
||||
if (this.hasRotatingPoint) {
|
||||
rotate.src = rotationImage;
|
||||
rotateLeft = left + width / 2 - 6;
|
||||
rotateTop = top - this.rotatingPointOffset - 6;
|
||||
ctx.drawImage(rotate, rotateLeft, rotateTop, 32, 32);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
function initImageEditor(data, fileUrl) {
|
||||
var imageEditor;
|
||||
var ps;
|
||||
var blackTheme = {
|
||||
'common.bi.image': '',
|
||||
'common.bisize.width': '0',
|
||||
'common.bisize.height': '0',
|
||||
'common.backgroundImage': 'none',
|
||||
'common.backgroundColor': '#1e1e1e',
|
||||
'common.border': '0px',
|
||||
|
||||
// header
|
||||
'header.backgroundImage': 'none',
|
||||
'header.backgroundColor': 'transparent',
|
||||
'header.border': '0px',
|
||||
|
||||
// load button
|
||||
'loadButton.backgroundColor': '#fff',
|
||||
'loadButton.border': '1px solid #ddd',
|
||||
'loadButton.color': '#222',
|
||||
'loadButton.fontFamily': '\'Noto Sans\', sans-serif',
|
||||
'loadButton.fontSize': '12px',
|
||||
|
||||
// download button
|
||||
'downloadButton.backgroundColor': '#fdba3b',
|
||||
'downloadButton.border': '1px solid #fdba3b',
|
||||
'downloadButton.color': '#fff',
|
||||
'downloadButton.fontFamily': '\'Noto Sans\', sans-serif',
|
||||
'downloadButton.fontSize': '12px',
|
||||
|
||||
// main icons
|
||||
'menu.normalIcon.path': '/images/icon-d.svg',
|
||||
'menu.normalIcon.name': 'icon-d',
|
||||
'menu.activeIcon.path': '/images/icon-b.svg',
|
||||
'menu.activeIcon.name': 'icon-b',
|
||||
'menu.disabledIcon.path': '/images/icon-a.svg',
|
||||
'menu.disabledIcon.name': 'icon-a',
|
||||
'menu.hoverIcon.path': '/images/icon-c.svg',
|
||||
'menu.hoverIcon.name': 'icon-c',
|
||||
'menu.iconSize.width': '24px',
|
||||
'menu.iconSize.height': '24px',
|
||||
|
||||
// submenu primary color
|
||||
'submenu.backgroundColor': '#1e1e1e',
|
||||
'submenu.partition.color': '#3c3c3c',
|
||||
|
||||
// submenu icons
|
||||
'submenu.normalIcon.path': '/images/icon-d.svg',
|
||||
'submenu.normalIcon.name': 'icon-d',
|
||||
'submenu.activeIcon.path': '/images/icon-c.svg',
|
||||
'submenu.activeIcon.name': 'icon-c',
|
||||
'submenu.iconSize.width': '32px',
|
||||
'submenu.iconSize.height': '32px',
|
||||
|
||||
// submenu labels
|
||||
'submenu.normalLabel.color': '#8a8a8a',
|
||||
'submenu.normalLabel.fontWeight': 'lighter',
|
||||
'submenu.activeLabel.color': '#fff',
|
||||
'submenu.activeLabel.fontWeight': 'lighter',
|
||||
|
||||
// checkbox style
|
||||
'checkbox.border': '0px',
|
||||
'checkbox.backgroundColor': '#fff',
|
||||
|
||||
// range style
|
||||
'range.pointer.color': '#fff',
|
||||
'range.bar.color': '#666',
|
||||
'range.subbar.color': '#d1d1d1',
|
||||
|
||||
'range.disabledPointer.color': '#414141',
|
||||
'range.disabledBar.color': '#282828',
|
||||
'range.disabledSubbar.color': '#414141',
|
||||
|
||||
'range.value.color': '#fff',
|
||||
'range.value.fontWeight': 'lighter',
|
||||
'range.value.fontSize': '11px',
|
||||
'range.value.border': '1px solid #353535',
|
||||
'range.value.backgroundColor': '#151515',
|
||||
'range.title.color': '#fff',
|
||||
'range.title.fontWeight': 'lighter',
|
||||
|
||||
// colorpicker style
|
||||
'colorpicker.button.border': '1px solid #1e1e1e',
|
||||
'colorpicker.title.color': '#fff'
|
||||
};
|
||||
|
||||
animateSpinner(null, true);
|
||||
imageEditor = new tui.ImageEditor('#tui-image-editor', {
|
||||
includeUI: {
|
||||
loadImage: {
|
||||
path: fileUrl,
|
||||
name: data.filename
|
||||
},
|
||||
theme: blackTheme,
|
||||
initMenu: 'draw',
|
||||
menuBarPosition: 'bottom'
|
||||
},
|
||||
cssMaxWidth: 700,
|
||||
cssMaxHeight: 500,
|
||||
selectionStyle: {
|
||||
cornerSize: 20,
|
||||
rotatingPointOffset: 70,
|
||||
borderColor: '#333',
|
||||
cornerColor: '#333',
|
||||
cornerStyle: 'circle',
|
||||
borderScaleFactor: 3
|
||||
|
||||
},
|
||||
usageStatistics: false
|
||||
});
|
||||
|
||||
imageEditor.on('image_loaded', () => {
|
||||
$('.file-save-link').css('display', '');
|
||||
animateSpinner(null, false);
|
||||
});
|
||||
|
||||
ps = new PerfectScrollbar($('.tui-image-editor-wrap')[0], { wheelSpeed: 0.5 });
|
||||
$('#tui-image-editor .tui-image-editor').on('mousewheel', (e) => {
|
||||
var imageOriginalSize = {
|
||||
width: imageEditor._graphics.canvasImage.width,
|
||||
height: imageEditor._graphics.canvasImage.height
|
||||
};
|
||||
var wDelta = e.originalEvent.wheelDelta || e.originalEvent.deltaY;
|
||||
var imageEditorWindow = e.currentTarget;
|
||||
var scrollContainer = $('.tui-image-editor-wrap');
|
||||
var initWidth = imageEditorWindow.style.width;
|
||||
var initHeight = imageEditorWindow.style.height;
|
||||
|
||||
var scrollContainerInitial = {
|
||||
top: scrollContainer.scrollTop(),
|
||||
left: scrollContainer.scrollLeft(),
|
||||
height: scrollContainer[0].scrollHeight,
|
||||
width: scrollContainer[0].scrollWidth
|
||||
};
|
||||
|
||||
var mousePosition = {
|
||||
top: e.clientY - (imageEditorWindow.offsetTop - scrollContainerInitial.top),
|
||||
left: e.clientX - $(imageEditorWindow).offset().left
|
||||
};
|
||||
|
||||
|
||||
var newWidth;
|
||||
var newHeight;
|
||||
var offsetY;
|
||||
var offsetX;
|
||||
if (wDelta > 0) {
|
||||
newWidth = parseInt(initWidth, 10) * 1.1;
|
||||
newHeight = parseInt(initHeight, 10) * 1.1;
|
||||
if (newWidth > imageOriginalSize.width || newHeight > imageOriginalSize.height) {
|
||||
newWidth = imageOriginalSize.width;
|
||||
newHeight = imageOriginalSize.height;
|
||||
}
|
||||
} else {
|
||||
newWidth = parseInt(initWidth, 10) * 0.9;
|
||||
newHeight = parseInt(initHeight, 10) * 0.9;
|
||||
if (parseInt(imageEditorWindow.dataset.minWidth, 10) * 0.5 > parseInt(newWidth, 10)) {
|
||||
newWidth = parseInt(imageEditorWindow.dataset.minWidth, 10) * 0.5;
|
||||
newHeight = parseInt(imageEditorWindow.dataset.minHeight, 10) * 0.5;
|
||||
}
|
||||
}
|
||||
imageEditorWindow.style.width = newWidth + 'px';
|
||||
imageEditorWindow.style.height = newHeight + 'px';
|
||||
$(imageEditorWindow).find('canvas, .tui-image-editor-canvas-container')
|
||||
.css('max-width', imageEditorWindow.style.width)
|
||||
.css('max-height', imageEditorWindow.style.height);
|
||||
if (imageEditorWindow.dataset.minHeight === undefined) {
|
||||
imageEditorWindow.dataset.minHeight = initHeight;
|
||||
imageEditorWindow.dataset.minWidth = initWidth;
|
||||
}
|
||||
|
||||
offsetY = (scrollContainer[0].scrollHeight - scrollContainerInitial.height)
|
||||
* (mousePosition.top / scrollContainerInitial.height);
|
||||
offsetX = (scrollContainer[0].scrollWidth - scrollContainerInitial.width)
|
||||
* (mousePosition.left / scrollContainerInitial.width);
|
||||
|
||||
scrollContainer.scrollTop(scrollContainerInitial.top + offsetY);
|
||||
scrollContainer.scrollLeft(scrollContainerInitial.left + offsetX);
|
||||
|
||||
ps.update();
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('.tui-image-editor-wrap')[0].onwheel = function() { return false; };
|
||||
$('.tui-image-editor-wrap').css('height', 'calc(100% - 150px)');
|
||||
|
||||
$('#fileEditModal').find('.file-name').text('Editing: ' + data.filename);
|
||||
$('#fileEditModal').modal('show');
|
||||
|
||||
$('.tui-image-editor-header').hide();
|
||||
|
||||
$('.file-save-link').css('display', 'none');
|
||||
$('.file-save-link').off().click(function(ev) {
|
||||
var imageBlob;
|
||||
var imageDataURL;
|
||||
var imageParams;
|
||||
var dataUpload = new FormData();
|
||||
var blobArray;
|
||||
var bytePosition;
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (data['mime-type'] === 'image/png') {
|
||||
imageParams = { format: 'png' };
|
||||
} else {
|
||||
imageParams = { format: 'jpeg', quality: (data.quality / 100) };
|
||||
}
|
||||
|
||||
imageDataURL = imageEditor.toDataURL(imageParams);
|
||||
imageDataURL = atob(imageDataURL.split(',')[1]);
|
||||
|
||||
blobArray = new Uint8Array(imageDataURL.length);
|
||||
|
||||
for (bytePosition = 0; bytePosition < imageDataURL.length; bytePosition += 1) {
|
||||
blobArray[bytePosition] = imageDataURL.charCodeAt(bytePosition);
|
||||
}
|
||||
|
||||
imageBlob = new Blob([blobArray]);
|
||||
|
||||
function closeEditor() {
|
||||
animateSpinner(null, false);
|
||||
imageEditor.destroy();
|
||||
imageEditor = {};
|
||||
$('#tui-image-editor').html('');
|
||||
$('#fileEditModal').modal('hide');
|
||||
}
|
||||
|
||||
dataUpload.append('image', imageBlob);
|
||||
animateSpinner(null, true);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/files/' + data.id + '/update_image',
|
||||
data: dataUpload,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(res) {
|
||||
$(`.asset[data-asset-id=${data.id}]`).replaceWith(res.html);
|
||||
$(`.asset[data-asset-id=${data.id}]`).closest('.attachments').trigger('reorder');
|
||||
closeEditor();
|
||||
}
|
||||
});
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
});
|
||||
|
||||
window.onresize = function() {
|
||||
imageEditor.ui.resizeEditor();
|
||||
};
|
||||
}
|
||||
|
||||
function preInitImageEditor() {
|
||||
$(document).on('click', '.image-edit-button', function() {
|
||||
var editButton = $(this);
|
||||
updateFabricControls();
|
||||
$.get(editButton.data('image-url'), function(responseData) {
|
||||
var fileUrl = responseData;
|
||||
var data = {
|
||||
id: editButton.data('image-id'),
|
||||
quality: editButton.data('image-quality'),
|
||||
filename: editButton.data('image-name'),
|
||||
'mime-type': editButton.data('image-mime-type')
|
||||
};
|
||||
$('#filePreviewModal').modal('hide');
|
||||
$.post(editButton.data('image-start-edit-url'));
|
||||
initImageEditor(data, fileUrl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
init: preInitImageEditor
|
||||
});
|
||||
}());
|
||||
ImageEditorModal.init();
|
|
@ -1,5 +1,4 @@
|
|||
/* global TinyMCE, ChemicalizeMarvinJs, MarvinJSUtil, I18n, FilePreviewModal, tinymce */
|
||||
/* global Results, Comments */
|
||||
/* global TinyMCE, ChemicalizeMarvinJs, MarvinJSUtil, I18n, tinymce, HelperModule */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable wrap-iife */
|
||||
/* eslint-disable no-use-before-define */
|
||||
|
@ -166,7 +165,6 @@ var MarvinJsEditorApi = (function() {
|
|||
TinyMCE.updateImages(config.editor);
|
||||
}
|
||||
$(marvinJsModal).modal('hide');
|
||||
FilePreviewModal.init();
|
||||
config.button.dataset.inProgress = false;
|
||||
}).error((response) => {
|
||||
if (response.status === 403) {
|
||||
|
@ -319,7 +317,17 @@ var MarvinJsEditorApi = (function() {
|
|||
})();
|
||||
|
||||
// Initialization
|
||||
|
||||
$(document).on('click', '.marvinjs-edit-button', function() {
|
||||
var editButton = $(this);
|
||||
$.post(editButton.data('sketch-start-edit-url'));
|
||||
$('#filePreviewModal').modal('hide');
|
||||
MarvinJsEditor.open({
|
||||
mode: 'edit',
|
||||
data: editButton.data('sketch-description'),
|
||||
name: editButton.data('sketch-name'),
|
||||
marvinUrl: editButton.data('update-url')
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
MarvinJsEditor = MarvinJsEditorApi();
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
$(this).find('.modal-body #repository_row-info-table').DataTable().destroy();
|
||||
$(this).remove();
|
||||
});
|
||||
FilePreviewModal.init();
|
||||
$('#repository_row-info-table').DataTable({
|
||||
dom: 'RBltpi',
|
||||
stateSave: false,
|
||||
|
|
|
@ -143,28 +143,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
button.new-marvinjs-upload-button {
|
||||
padding: 1px 16px !important;
|
||||
|
||||
.new-marvinjs-upload-button {
|
||||
.new-marvinjs-upload-icon {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.new-marvinjs-upload-button {
|
||||
|
||||
.new-marvinjs-upload-icon {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
margin-left: -9px;
|
||||
width: 24px;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
|
|
|
@ -57,9 +57,11 @@
|
|||
|
||||
// Create wopi file
|
||||
.create-wopi-file-btn {
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
margin-right: 5px;
|
||||
height: 20px;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,12 @@
|
|||
|
||||
.fas {
|
||||
padding-right: .5em;
|
||||
width: 1.75em;
|
||||
}
|
||||
|
||||
.create-wopi-file-btn {
|
||||
.create-wopi-file-btn,
|
||||
.new-marvinjs-upload-button {
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
margin-left: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -369,6 +369,10 @@ label {
|
|||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Result table element style
|
||||
|
@ -422,6 +426,11 @@ label {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.report-element-children {
|
||||
height: 0;
|
||||
}
|
||||
|
@ -453,6 +462,7 @@ label {
|
|||
.report-element-header {
|
||||
.file-name {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,10 @@ div.print-report {
|
|||
&:hover > .report-element-header .attachment-icon {
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.report-step-table-element {
|
||||
|
@ -195,6 +199,10 @@ div.print-report {
|
|||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&:hover > .report-element-header {
|
||||
.result-icon,
|
||||
.result-name,
|
||||
|
|
302
app/assets/stylesheets/shared/assets.scss
Normal file
302
app/assets/stylesheets/shared/assets.scss
Normal file
|
@ -0,0 +1,302 @@
|
|||
// scss-lint:disable ImportantRule PropertyUnits NestingDepth SelectorDepth
|
||||
|
||||
.attachment-container {
|
||||
@include md-card-style;
|
||||
grid-row: span 6;
|
||||
height: 23em;
|
||||
overflow: hidden;
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
width: var(--attachment-column-width);
|
||||
|
||||
|
||||
.file-preview-link {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
border-radius: $border-radius-default;
|
||||
color: $color-volcano;
|
||||
height: 14em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
&.processing {
|
||||
background-image: url("/images/medium/processing.gif");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.marvinjs {
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
border-radius: 1em 0 0 1em;
|
||||
bottom: 1em;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
position: absolute;
|
||||
right: -1em;
|
||||
width: 2.25em;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: $marvinjs-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background-image: url("/images/icon_small/marvinjs-white.svg");
|
||||
height: 2.25em;
|
||||
right: -.75em;
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.fas {
|
||||
font-size: 100px;
|
||||
line-height: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-shadow {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.attachment-label,
|
||||
.attachment-metadata {
|
||||
background: $color-white;
|
||||
overflow: hidden;
|
||||
padding-top: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
transition: $md-transaction;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attachment-label {
|
||||
@include font-main;
|
||||
margin-top: 1.5em;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.attachment-metadata {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
margin-top: -4em;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
bottom: .5em;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: .5em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $md-shadow;
|
||||
|
||||
.file-preview-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.remove-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.attachment-label,
|
||||
.attachment-metadata {
|
||||
margin-top: 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.new {
|
||||
border: 1px solid $brand-primary;
|
||||
|
||||
.dnd-error {
|
||||
bottom: 15px;
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: $brand-primary;
|
||||
border-radius: 0 $border-radius-default;
|
||||
bottom: 0;
|
||||
color: $color-white;
|
||||
content: "NEW";
|
||||
left: 0;
|
||||
line-height: 20px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-context-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-attachment-container {
|
||||
@include md-card-style;
|
||||
grid-column: 1/-1;
|
||||
grid-row: span 12;
|
||||
height: 47em;
|
||||
|
||||
.active-iframe-preview {
|
||||
border: 0;
|
||||
height: calc(100% - 4em);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-container,
|
||||
.general-file-container {
|
||||
align-items: center;
|
||||
background: $color-concrete;
|
||||
display: flex;
|
||||
height: calc(100% - 4em);
|
||||
justify-content: center;
|
||||
padding: .5em;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.fas {
|
||||
font-size: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 4em;
|
||||
padding: 0 1em;
|
||||
|
||||
.show-as-thumbnail {
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
@include font-main;
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
.file-metadata {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
display: grid;
|
||||
grid-column-gap: 1em;
|
||||
grid-template-columns: max-content max-content;
|
||||
}
|
||||
|
||||
.asset-context-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-attachment-container {
|
||||
@include md-card-style;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
grid-column: 1/-1;
|
||||
height: 3em;
|
||||
padding: .5em;
|
||||
|
||||
.file-icon {
|
||||
@include font-main;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
@include font-main;
|
||||
color: $brand-primary;
|
||||
margin: 0 .5em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-metadata {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
display: grid;
|
||||
grid-column-gap: 1em;
|
||||
grid-template-columns: max-content max-content;
|
||||
}
|
||||
|
||||
.asset-context-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-context-menu {
|
||||
display: inline-block;
|
||||
|
||||
#dropdownAssetContextMenu {
|
||||
background: $color-white;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: 200px;
|
||||
padding: .5em 0;
|
||||
|
||||
.divider-label {
|
||||
@include font-small;
|
||||
color: $color-alto;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.change-preview-type,
|
||||
.delete-asset {
|
||||
.fas {
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
145
app/assets/stylesheets/shared/file_preview.scss
Normal file
145
app/assets/stylesheets/shared/file_preview.scss
Normal file
|
@ -0,0 +1,145 @@
|
|||
// scss-lint:disable ImportantRule PropertyUnits NestingDepth SelectorDepth
|
||||
|
||||
// Image preview modal
|
||||
.modal-file-preview {
|
||||
background: transparent;
|
||||
font-size: $font-size-base;
|
||||
padding: 0 !important;
|
||||
|
||||
.modal-dialog {
|
||||
background: inherit;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: inherit;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.file-preview-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
&.processing {
|
||||
background-image: url("/images/medium/processing.gif");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.wopi-file-preview {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
background: url(asset-path("custom/checkerboard-pattern.png"));
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
|
||||
@media (max-height: 520px) {
|
||||
height: 80%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
align-items: center;
|
||||
background: $color-white;
|
||||
display: flex;
|
||||
height: 4em;
|
||||
padding: 0 1em;
|
||||
|
||||
.file-name {
|
||||
font-weight: bold;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.asset-context-menu {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
height: calc(100vh - 8em);
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: $color-white;
|
||||
height: 4em;
|
||||
padding: 0 1em;
|
||||
|
||||
.gallery {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
height: 100%;
|
||||
padding: 0 1em;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
max-height: 2em;
|
||||
max-width: 2em;
|
||||
}
|
||||
|
||||
|
||||
.file-counter {
|
||||
font-weight: bold;
|
||||
grid-column-start: 2;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gallery-switcher {
|
||||
align-items: center;
|
||||
color: $color-black;
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
display: inline-block;
|
||||
max-width: 16em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.fa-angle-right,
|
||||
.fa-angle-left {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
|
||||
&.previous-asset {
|
||||
grid-column-start: 1;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
&.next-asset {
|
||||
grid-column-start: 3;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
app/assets/stylesheets/shared/image_edit_modal.scss
Normal file
37
app/assets/stylesheets/shared/image_edit_modal.scss
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Image edit modal
|
||||
.modal-file-edit {
|
||||
background: transparent;
|
||||
font-size: $font-size-base;
|
||||
padding: 0 !important;
|
||||
|
||||
.modal-dialog {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
align-items: center;
|
||||
background: $color-white;
|
||||
display: flex;
|
||||
height: 4em;
|
||||
padding: 0 1em;
|
||||
|
||||
.file-name {
|
||||
font-weight: bold;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: calc(100% - 4em);
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16,9 +16,10 @@
|
|||
transition: .3s;
|
||||
user-select: none;
|
||||
|
||||
.fas {
|
||||
.fas,
|
||||
img {
|
||||
color: inherit;
|
||||
margin-right: 5px;
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -5,10 +5,17 @@
|
|||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
// scss-lint:disable ImportantRule
|
||||
// scss-lint:disable Unknown
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
:root {
|
||||
--attachment-column-width: 16em;
|
||||
--attachment-row-height: 3em;
|
||||
}
|
||||
|
||||
|
||||
#new_step,
|
||||
.panel-step-attachment {
|
||||
ul {
|
||||
|
@ -145,194 +152,18 @@
|
|||
|
||||
.attachments {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-auto-rows: var(--attachment-row-height);
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--attachment-column-width), 1fr));
|
||||
margin: 1em 0;
|
||||
|
||||
.pseudo-attachment-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
.file-preview-link {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.new {
|
||||
order: 0 !important;
|
||||
|
||||
.file-preview-link {
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.attachment-placeholder {
|
||||
border: 1px solid $brand-primary;
|
||||
|
||||
&::before {
|
||||
background: $brand-primary;
|
||||
border-radius: 0 5px;
|
||||
bottom: 16px;
|
||||
color: $color-white;
|
||||
content: "NEW";
|
||||
left: 8px;
|
||||
line-height: 20px;
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.nested_fields {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-placeholder {
|
||||
@include md-card-style;
|
||||
color: $color-silver-chalice;
|
||||
height: 280px;
|
||||
margin: 4px 8px 16px;
|
||||
text-align: center;
|
||||
width: 220px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.new {
|
||||
background-color: rgba(95, 95, 95, .1);
|
||||
}
|
||||
|
||||
.attachment-thumbnail {
|
||||
display: inline-block;
|
||||
height: 160px;
|
||||
margin: 16px 0 5px;
|
||||
overflow: hidden;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
border-radius: 5px;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.marvinjs {
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
border-radius: 16px 0 0 16px;
|
||||
display: block;
|
||||
height: 32px;
|
||||
right: 0;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: $marvinjs-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background-image: url("/images/icon_small/marvinjs-white.svg");
|
||||
right: 4px;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
i.fas {
|
||||
font-size: 100px;
|
||||
line-height: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-shadow {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.attachment-label {
|
||||
background: $color-white;
|
||||
color: $brand-primary;
|
||||
font-family: Lato;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
line-height: 18px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top: 20px;
|
||||
transition: $md-transaction;
|
||||
width: 190px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.spencer-bonnet-modif {
|
||||
align-items: center;
|
||||
color: $color-silver-chalice;
|
||||
display: flex;
|
||||
font-family: Lato;
|
||||
font-size: 12px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
margin: 0 auto 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top: -20px;
|
||||
transition: $md-transaction;
|
||||
width: 149px;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
bottom: 15px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
position: relative;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $md-shadow;
|
||||
|
||||
.remove-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.attachment-label {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.spencer-bonnet-modif {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.new {
|
||||
background-color: rgba(95, 95, 95, .1);
|
||||
|
||||
.attachment-label {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dnd-error {
|
||||
bottom: 15px;
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.attachment-label {
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachments-order {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.attachments-actions {
|
||||
align-items: center;
|
||||
|
@ -344,18 +175,45 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attachments-order {
|
||||
color: $color-silver-chalice;
|
||||
min-width: 140px;
|
||||
}
|
||||
.dropdown-attachment-options {
|
||||
@include font-button;
|
||||
min-width: 200px;
|
||||
|
||||
.btn-default {
|
||||
border: 0;
|
||||
color: inherit;
|
||||
margin-left: 10px;
|
||||
.divider-label {
|
||||
@include font-small;
|
||||
color: $color-alto;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: inherit;
|
||||
a {
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
.change-order {
|
||||
padding-left: 2.75em;
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.attachments-view-mode {
|
||||
.fas {
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1442,209 +1442,6 @@ ul.content-activities {
|
|||
}
|
||||
}
|
||||
|
||||
.attachment-thumbnail {
|
||||
display: inline-block;
|
||||
height: 160px;
|
||||
margin: 16px 10px 5px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
border-radius: 5px;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.fas {
|
||||
font-size: 100px;
|
||||
line-height: 160px;
|
||||
}
|
||||
|
||||
&.processing {
|
||||
background-image: url("/images/medium/processing.gif");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
// Image preview modal
|
||||
.modal-file-preview {
|
||||
background: transparent;
|
||||
font-size: $font-size-large;
|
||||
padding-right: 0 !important;
|
||||
z-index: 1060;
|
||||
|
||||
.preview-close {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.file-wopi-controls {
|
||||
display: inline-block;
|
||||
|
||||
.btn {
|
||||
margin: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
color: $color-white;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.file-preview-container {
|
||||
align-items: center;
|
||||
color: $gray-dark;
|
||||
display: -moz-flex;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 60%;
|
||||
|
||||
&.processing {
|
||||
background-image: url("/images/medium/processing.gif");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
color: $color-black;
|
||||
margin: 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
background: url(asset-path("custom/checkerboard-pattern.png"));
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
|
||||
@media (max-height: 520px) {
|
||||
height: 80%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: $color-black;
|
||||
border: 0;
|
||||
height: 60px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
|
||||
.file-name {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
align-items: center;
|
||||
display: -moz-flex;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
height: calc(100% - 120px);
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: $color-black;
|
||||
border: 0;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-download-link {
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.file-edit-link {
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Image edit modal
|
||||
.modal-file-edit {
|
||||
background: transparent;
|
||||
font-size: $font-size-large;
|
||||
padding: 0 !important;
|
||||
|
||||
.preview-close {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
color: $color-white;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: $color-black;
|
||||
border: 0;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
|
||||
.file-name {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: calc(100% - 60px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.file-save-link {
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable textarea resizing throughout application
|
||||
// (will be done via autosize JS plugin)
|
||||
textarea {
|
||||
|
|
|
@ -14,86 +14,38 @@ class AssetsController < ApplicationController
|
|||
include FileIconsHelper
|
||||
include MyModulesHelper
|
||||
|
||||
helper_method :wopi_file_edit_button_status
|
||||
|
||||
before_action :load_vars, except: :create_wopi_file
|
||||
before_action :check_read_permission, except: :edit
|
||||
before_action :check_edit_permission, only: :edit
|
||||
before_action :check_read_permission, except: %i(edit destroy create_wopi_file)
|
||||
before_action :check_edit_permission, only: %i(edit destroy)
|
||||
|
||||
def file_preview
|
||||
file_type = @asset.file.metadata[:asset_type] || (@asset.previewable? ? 'previewable' : false)
|
||||
response_json = {
|
||||
'id' => @asset.id,
|
||||
'type' => file_type,
|
||||
'filename' => truncate(escape_input(@asset.file_name),
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
'download-url' => asset_file_url_path(@asset)
|
||||
}
|
||||
|
||||
can_edit = if @assoc.class == Step
|
||||
can_manage_protocol_in_module?(@protocol) || can_manage_protocol_in_repository?(@protocol)
|
||||
elsif @assoc.class == Result
|
||||
can_manage_module?(@my_module)
|
||||
elsif @assoc.class == RepositoryCell && !@repository.is_a?(RepositorySnapshot)
|
||||
can_manage_repository_rows?(@repository)
|
||||
end
|
||||
if response_json['type'] == 'previewable'
|
||||
if ['image/jpeg', 'image/pjpeg'].include? @asset.file.content_type
|
||||
response_json['quality'] = @asset.file_image_quality || 80
|
||||
end
|
||||
response_json.merge!(
|
||||
'editable' => @asset.editable_image? && can_edit,
|
||||
'mime-type' => @asset.file.content_type,
|
||||
'large-preview-url' => rails_representation_url(@asset.large_preview)
|
||||
)
|
||||
elsif response_json['type'] == 'marvinjs'
|
||||
response_json.merge!(
|
||||
'editable' => can_edit,
|
||||
'large-preview-url' => rails_representation_url(@asset.large_preview),
|
||||
'update-url' => marvin_js_asset_path(@asset.id),
|
||||
'description' => @asset.file.metadata[:description],
|
||||
'name' => @asset.file.metadata[:name]
|
||||
)
|
||||
else
|
||||
|
||||
response_json['preview-icon'] = render_to_string(partial: 'shared/file_preview_icon.html.erb',
|
||||
locals: { asset: @asset })
|
||||
end
|
||||
|
||||
if wopi_enabled? && wopi_file?(@asset)
|
||||
edit_supported, title = wopi_file_edit_button_status
|
||||
response_json['wopi-controls'] = render_to_string(
|
||||
partial: 'assets/wopi/file_wopi_controls.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
can_edit: can_edit,
|
||||
edit_supported: edit_supported,
|
||||
title: title
|
||||
}
|
||||
)
|
||||
end
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: response_json
|
||||
end
|
||||
end
|
||||
render json: { html: render_to_string(
|
||||
partial: 'shared/file_preview/content.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
can_edit: can_manage_asset?(@asset),
|
||||
gallery: params[:gallery]
|
||||
}
|
||||
) }
|
||||
end
|
||||
|
||||
# Check whether the wopi file can be edited and return appropriate response
|
||||
def wopi_file_edit_button_status
|
||||
file_ext = @asset.file_name.split('.').last
|
||||
if Constants::WOPI_EDITABLE_FORMATS.include?(file_ext)
|
||||
edit_supported = true
|
||||
title = ''
|
||||
else
|
||||
edit_supported = false
|
||||
title = if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
I18n.t('assets.wopi_supported_text_formats_title')
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
I18n.t('assets.wopi_supported_table_formats_title')
|
||||
else
|
||||
I18n.t('assets.wopi_supported_presentation_formats_title')
|
||||
end
|
||||
def toggle_view_mode
|
||||
@asset.view_mode = toggle_view_mode_params[:view_mode]
|
||||
if @asset.save(touch: false)
|
||||
gallery_view_id = @assoc.id if @assoc.class == Step
|
||||
|
||||
html = render_to_string(partial: 'assets/asset.html.erb', locals: {
|
||||
asset: @asset,
|
||||
gallery_view_id: gallery_view_id
|
||||
})
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { html: html }, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
return edit_supported, title
|
||||
end
|
||||
|
||||
def file_url
|
||||
|
@ -113,7 +65,7 @@ class AssetsController < ApplicationController
|
|||
tkn = current_user.get_wopi_token
|
||||
@token = tkn.token
|
||||
@ttl = (tkn.ttl * 1000).to_s
|
||||
@asset.step&.protocol&.update(updated_at: Time.now)
|
||||
@asset.step&.protocol&.update(updated_at: Time.zone.now)
|
||||
|
||||
create_wopi_file_activity(current_user, true)
|
||||
|
||||
|
@ -147,41 +99,17 @@ class AssetsController < ApplicationController
|
|||
@asset.team.release_space(orig_file_size)
|
||||
# Post process file here
|
||||
@asset.post_process_file(@asset.team)
|
||||
@asset.step&.protocol&.update(updated_at: Time.now)
|
||||
@asset.step&.protocol&.update(updated_at: Time.zone.now)
|
||||
|
||||
render_html = if @asset.step
|
||||
assets = @asset.step.assets
|
||||
order_atoz = az_ordered_assets_index(@asset.step, @asset.id)
|
||||
order_ztoa = assets.length - az_ordered_assets_index(@asset.step, @asset.id)
|
||||
asset_position = @asset.step.asset_position(@asset)
|
||||
render_html = if @asset.step || @asset.result
|
||||
render_to_string(
|
||||
partial: 'steps/attachments/item.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
i: asset_position[:pos],
|
||||
assets_count: asset_position[:count],
|
||||
step: @asset.step,
|
||||
order_atoz: order_atoz,
|
||||
order_ztoa: order_ztoa
|
||||
},
|
||||
formats: :html
|
||||
)
|
||||
elsif @asset.result
|
||||
render_to_string(
|
||||
partial: 'steps/attachments/item.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
i: 0,
|
||||
assets_count: 0,
|
||||
step: nil,
|
||||
order_atoz: 0,
|
||||
order_ztoa: 0
|
||||
},
|
||||
partial: 'assets/asset.html.erb',
|
||||
locals: { asset: @asset },
|
||||
formats: :html
|
||||
)
|
||||
else
|
||||
render_to_string(
|
||||
partial: 'shared/asset_link',
|
||||
partial: 'assets/asset_link.html.erb',
|
||||
locals: { asset: @asset, display_image_tag: true },
|
||||
formats: :html
|
||||
)
|
||||
|
@ -211,7 +139,7 @@ class AssetsController < ApplicationController
|
|||
unless asset.valid?(:wopi_file_creation)
|
||||
render json: {
|
||||
message: asset.errors
|
||||
}, status: 400 and return
|
||||
}, status: :bad_request and return
|
||||
end
|
||||
|
||||
# Create file depending on the type
|
||||
|
@ -220,7 +148,7 @@ class AssetsController < ApplicationController
|
|||
render_403 && return unless can_manage_protocol_in_module?(step.protocol) ||
|
||||
can_manage_protocol_in_repository?(step.protocol)
|
||||
step_asset = StepAsset.create!(step: step, asset: asset)
|
||||
step.protocol&.update(updated_at: Time.now)
|
||||
step.protocol&.update(updated_at: Time.zone.now)
|
||||
|
||||
edit_url = edit_asset_url(step_asset.asset_id)
|
||||
elsif params[:element_type] == 'Result'
|
||||
|
@ -248,10 +176,18 @@ class AssetsController < ApplicationController
|
|||
}, status: :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @asset.destroy
|
||||
render json: { flash: I18n.t('assets.file_deleted', file_name: @asset.file_name ) }
|
||||
else
|
||||
render json: {}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@asset = Asset.find_by_id(params[:id])
|
||||
@asset = Asset.find_by(id: params[:id])
|
||||
return render_404 unless @asset
|
||||
|
||||
@assoc ||= @asset.step
|
||||
|
@ -268,25 +204,11 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_read_permission
|
||||
if @assoc.class == Step
|
||||
render_403 && return unless can_read_protocol_in_module?(@protocol) ||
|
||||
can_read_protocol_in_repository?(@protocol)
|
||||
elsif @assoc.class == Result
|
||||
render_403 and return unless can_read_experiment?(@my_module.experiment)
|
||||
elsif @assoc.class == RepositoryCell
|
||||
render_403 and return unless can_read_repository?(@repository)
|
||||
end
|
||||
render_403 and return unless can_read_asset?(@asset)
|
||||
end
|
||||
|
||||
def check_edit_permission
|
||||
if @assoc.class == Step
|
||||
render_403 && return unless can_manage_protocol_in_module?(@protocol) ||
|
||||
can_manage_protocol_in_repository?(@protocol)
|
||||
elsif @assoc.class == Result
|
||||
render_403 and return unless can_manage_module?(@my_module)
|
||||
elsif @assoc.class == RepositoryCell
|
||||
render_403 and return unless can_manage_repository_rows?(@repository)
|
||||
end
|
||||
render_403 and return unless can_manage_asset?(@asset)
|
||||
end
|
||||
|
||||
def append_wd_params(url)
|
||||
|
@ -299,6 +221,10 @@ class AssetsController < ApplicationController
|
|||
params.permit(:file)
|
||||
end
|
||||
|
||||
def toggle_view_mode_params
|
||||
params.require(:asset).permit(:view_mode)
|
||||
end
|
||||
|
||||
def asset_data_type(asset)
|
||||
return 'wopi' if wopi_file?(asset)
|
||||
return 'image' if asset.image?
|
||||
|
|
|
@ -17,15 +17,10 @@ class MarvinJsAssetsController < ApplicationController
|
|||
|
||||
if result[:asset] && marvin_params[:object_type] == 'Step'
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'steps/attachments/item.html.erb',
|
||||
locals: { asset: result[:asset],
|
||||
i: 0,
|
||||
assets_count: 0,
|
||||
step: result[:object],
|
||||
order_atoz: 0,
|
||||
order_ztoa: 0 }
|
||||
)
|
||||
html: render_to_string(partial: 'assets/asset.html.erb', locals: {
|
||||
asset: result[:asset],
|
||||
gallery_view_id: marvin_params[:object_id]
|
||||
})
|
||||
}
|
||||
elsif result[:asset] && marvin_params[:object_type] == 'Result'
|
||||
@my_module = result[:object].my_module
|
||||
|
@ -63,7 +58,7 @@ class MarvinJsAssetsController < ApplicationController
|
|||
private
|
||||
|
||||
def load_vars
|
||||
@asset = current_team.assets.find_by_id(params[:id])
|
||||
@asset = current_team.assets.find_by(id: params[:id])
|
||||
return render_404 unless @asset
|
||||
|
||||
@assoc ||= @asset.step
|
||||
|
@ -77,8 +72,8 @@ class MarvinJsAssetsController < ApplicationController
|
|||
end
|
||||
|
||||
def load_create_vars
|
||||
@assoc = Step.find_by_id(marvin_params[:object_id]) if marvin_params[:object_type] == 'Step'
|
||||
@assoc = MyModule.find_by_id(params[:object_id]) if marvin_params[:object_type] == 'Result'
|
||||
@assoc = Step.find_by(id: marvin_params[:object_id]) if marvin_params[:object_type] == 'Step'
|
||||
@assoc = MyModule.find_by(id: params[:object_id]) if marvin_params[:object_type] == 'Result'
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @assoc.protocol
|
||||
|
|
|
@ -5,11 +5,11 @@ class StepsController < ApplicationController
|
|||
include MarvinJsActions
|
||||
|
||||
before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state
|
||||
move_up move_down)
|
||||
move_up move_down update_asset_view_mode)
|
||||
before_action :load_vars_nested, only: %i(new create)
|
||||
before_action :convert_table_contents_to_utf8, only: %i(create update)
|
||||
|
||||
before_action :check_view_permissions, only: %i(show update_view_state)
|
||||
before_action :check_view_permissions, only: %i(show update_view_state update_asset_view_mode)
|
||||
before_action :check_manage_permissions, only: %i(new create edit update destroy move_up move_down toggle_step_state)
|
||||
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state checklistitem_state)
|
||||
|
||||
|
@ -145,7 +145,12 @@ class StepsController < ApplicationController
|
|||
step_params_all[:assets_attributes]&.each do |key, value|
|
||||
next unless value[:signed_blob_id]
|
||||
|
||||
new_asset = @step.assets.create!(created_by: current_user, last_modified_by: current_user, team: current_team)
|
||||
new_asset = @step.assets.create!(
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
team: current_team,
|
||||
view_mode: @step.assets_view_mode
|
||||
)
|
||||
new_asset.file
|
||||
.attach(value[:signed_blob_id])
|
||||
new_assets.push(new_asset.id)
|
||||
|
@ -218,6 +223,25 @@ class StepsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def update_asset_view_mode
|
||||
html = ''
|
||||
ActiveRecord::Base.transaction do
|
||||
@step.assets_view_mode = params[:assets_view_mode]
|
||||
@step.save!(touch: false)
|
||||
@step.assets.update_all(view_mode: @step.assets_view_mode)
|
||||
end
|
||||
@step.assets.each do |asset|
|
||||
html += render_to_string(partial: 'assets/asset.html.erb', locals: {
|
||||
asset: asset,
|
||||
gallery_view_id: @step.id
|
||||
})
|
||||
end
|
||||
render json: { html: html }, status: :ok
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error(e.message)
|
||||
render json: { errors: e.message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @step.can_destroy?
|
||||
|
||||
|
|
|
@ -334,7 +334,7 @@ class WopiController < ActionController::Base
|
|||
url = request.original_url.upcase.encode('utf-8')
|
||||
|
||||
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
|
||||
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||
if wopi_verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||
logger.warn 'WOPI: proof verification: successful'
|
||||
else
|
||||
logger.warn 'WOPI: proof verification: not verified'
|
||||
|
|
|
@ -186,4 +186,23 @@ module ApplicationHelper
|
|||
def wopi_enabled?
|
||||
ENV['WOPI_ENABLED'] == 'true'
|
||||
end
|
||||
|
||||
# Check whether the wopi file can be edited and return appropriate response
|
||||
def wopi_file_edit_button_status(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
if Constants::WOPI_EDITABLE_FORMATS.include?(file_ext)
|
||||
edit_supported = true
|
||||
title = ''
|
||||
else
|
||||
edit_supported = false
|
||||
title = if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
I18n.t('assets.wopi_supported_text_formats_title')
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
I18n.t('assets.wopi_supported_table_formats_title')
|
||||
else
|
||||
I18n.t('assets.wopi_supported_presentation_formats_title')
|
||||
end
|
||||
end
|
||||
return edit_supported, title
|
||||
end
|
||||
end
|
||||
|
|
|
@ -99,9 +99,9 @@ module ReportsHelper
|
|||
|
||||
# "Hack" to omit file preview URL because of WKHTML issues
|
||||
def report_image_asset_url(asset)
|
||||
image_tag(asset.medium_preview
|
||||
.processed
|
||||
.service_url(expires_in: Constants::URL_LONG_EXPIRE_TIME))
|
||||
preview = asset.inline? ? asset.large_preview : asset.medium_preview
|
||||
image_tag(preview.processed
|
||||
.service_url(expires_in: Constants::URL_LONG_EXPIRE_TIME))
|
||||
end
|
||||
|
||||
# "Hack" to load Glyphicons css directly from the CDN
|
||||
|
|
|
@ -12,6 +12,8 @@ class Asset < ApplicationRecord
|
|||
# Lock duration set to 30 minutes
|
||||
LOCK_DURATION = 60 * 30
|
||||
|
||||
enum view_mode: { thumbnail: 0, inline: 1, list: 2 }
|
||||
|
||||
# ActiveStorage configuration
|
||||
has_one_attached :file
|
||||
|
||||
|
@ -315,7 +317,7 @@ class Asset < ApplicationRecord
|
|||
file_ext = file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
if !action.nil?
|
||||
action_url = action.urlsrc
|
||||
action_url = action[:urlsrc]
|
||||
if ENV['WOPI_BUSINESS_USERS'] && ENV['WOPI_BUSINESS_USERS'] == 'true'
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
|
||||
'IsLicensedUser=1&')
|
||||
|
@ -349,7 +351,7 @@ class Asset < ApplicationRecord
|
|||
def favicon_url(action)
|
||||
file_ext = file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
action.wopi_app.icon if action.try(:wopi_app)
|
||||
action[:icon] if action[:icon]
|
||||
end
|
||||
|
||||
# locked?, lock_asset and refresh_lock rely on the asset
|
||||
|
|
|
@ -4,6 +4,8 @@ class Step < ApplicationRecord
|
|||
include TinyMceImages
|
||||
include ViewableModel
|
||||
|
||||
enum assets_view_mode: { thumbnail: 0, inline: 1, list: 2 }
|
||||
|
||||
auto_strip_attributes :name, :description, nullify: false
|
||||
validates :name,
|
||||
presence: true,
|
||||
|
|
|
@ -4,9 +4,4 @@ class WopiAction < ApplicationRecord
|
|||
belongs_to :wopi_app, foreign_key: 'wopi_app_id', class_name: 'WopiApp'
|
||||
|
||||
validates :action, :extension, :urlsrc, :wopi_app, presence: true
|
||||
|
||||
def self.find_action(extension, activity)
|
||||
WopiAction.distinct
|
||||
.where('extension = ? and action = ?', extension, activity).first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,43 +13,4 @@ class WopiDiscovery < ApplicationRecord
|
|||
:proof_key_old_mod,
|
||||
:proof_key_old_exp,
|
||||
presence: true
|
||||
|
||||
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
|
||||
# with this discovery public key (two key possible old/new)
|
||||
def verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||
token_length = [token.length].pack('>N').bytes
|
||||
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
|
||||
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
|
||||
url_length = [url.length].pack('>N').bytes
|
||||
|
||||
expected_proof = token_length + token.bytes +
|
||||
url_length + url.upcase.bytes +
|
||||
timestamp_length + timestamp_bytes
|
||||
|
||||
key = generate_key(proof_key_mod, proof_key_exp)
|
||||
old_key = generate_key(proof_key_old_mod, proof_key_old_exp)
|
||||
|
||||
# Try all possible combiniations
|
||||
try_verification(expected_proof, signed_proof, key) ||
|
||||
try_verification(expected_proof, signed_proof_old, key) ||
|
||||
try_verification(expected_proof, signed_proof, old_key)
|
||||
end
|
||||
|
||||
# Generates a public key from given modulus and exponent
|
||||
def generate_key(modulus, exponent)
|
||||
mod = Base64.decode64(modulus).unpack('H*').first.to_i(16)
|
||||
exp = Base64.decode64(exponent).unpack('H*').first.to_i(16)
|
||||
|
||||
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
|
||||
OpenSSL::ASN1::Integer.new(exp)])
|
||||
OpenSSL::PKey::RSA.new(seq.to_der)
|
||||
end
|
||||
|
||||
# Verify if decrypting signed_proof with public_key equals to expected_proof
|
||||
def try_verification(expected_proof, signed_proof_b64, public_key)
|
||||
signed_proof = Base64.decode64(signed_proof_b64)
|
||||
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
|
||||
expected_proof.pack('c*'))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
33
app/permissions/asset.rb
Normal file
33
app/permissions/asset.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Canaid::Permissions.register_for(Asset) do
|
||||
can :read_asset do |user, asset|
|
||||
object = asset.step || asset.result || asset.repository_cell
|
||||
|
||||
case object
|
||||
when Step
|
||||
protocol = object.protocol
|
||||
can_read_protocol_in_module?(user, protocol) || can_read_protocol_in_repository?(user, protocol)
|
||||
when Result
|
||||
can_read_experiment?(user, object.my_module.experiment)
|
||||
when RepositoryCell
|
||||
can_read_repository?(user, object.repository_column.repository)
|
||||
end
|
||||
end
|
||||
|
||||
can :manage_asset do |user, asset|
|
||||
object = asset.step || asset.result || asset.repository_cell
|
||||
|
||||
case object
|
||||
when Step
|
||||
protocol = object.protocol
|
||||
can_manage_protocol_in_module?(user, protocol) || can_manage_protocol_in_repository?(user, protocol)
|
||||
when Result
|
||||
can_manage_experiment?(user, object.my_module.experiment)
|
||||
when RepositoryCell
|
||||
return false if object.repository_column.repository.is_a?(RepositorySnapshot)
|
||||
|
||||
can_manage_repository?(user, object.repository_column.repository)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,17 +7,22 @@ module Reports::Docx::DrawResultAsset
|
|||
|
||||
asset = result.asset
|
||||
timestamp = asset.created_at
|
||||
asset_url = Rails.application.routes.url_helpers.asset_download_url(asset)
|
||||
color = @color
|
||||
@docx.p
|
||||
@docx.p do
|
||||
text result.name, italic: true
|
||||
text ' '
|
||||
link I18n.t('projects.reports.elements.download'), asset_url do
|
||||
italic true
|
||||
end
|
||||
text ' ' + I18n.t('search.index.archived'), color: color[:gray] if result.archived?
|
||||
text ' ' + I18n.t('projects.reports.elements.result_asset.file_name', file: asset.file_name)
|
||||
text ' ' + I18n.t('projects.reports.elements.result_asset.user_time',
|
||||
user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
|
||||
end
|
||||
|
||||
Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
|
||||
Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.previewable? && !asset.list?
|
||||
|
||||
subject['children'].each do |child|
|
||||
public_send("draw_#{child['type_of']}", child, result)
|
||||
|
|
|
@ -6,15 +6,20 @@ module Reports::Docx::DrawStepAsset
|
|||
return unless asset
|
||||
|
||||
timestamp = asset.created_at
|
||||
asset_url = Rails.application.routes.url_helpers.asset_download_url(asset)
|
||||
color = @color
|
||||
@docx.p
|
||||
@docx.p do
|
||||
text (I18n.t 'projects.reports.elements.step_asset.file_name', file: asset.file_name), italic: true
|
||||
text ' '
|
||||
link I18n.t('projects.reports.elements.download'), asset_url do
|
||||
italic true
|
||||
end
|
||||
text ' '
|
||||
text I18n.t('projects.reports.elements.step_asset.user_time',
|
||||
timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
|
||||
end
|
||||
|
||||
Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
|
||||
Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.previewable? && !asset.list?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -141,9 +141,9 @@ module Reports
|
|||
def self.render_asset_image(docx, asset)
|
||||
return unless asset
|
||||
|
||||
image_path = Reports::Utils.image_path(asset.file)
|
||||
asset_preview = Reports::Utils.image_prepare(asset)
|
||||
|
||||
dimension = FastImage.size(image_path)
|
||||
dimension = FastImage.size(asset_preview.service_url)
|
||||
return unless dimension
|
||||
|
||||
x = dimension[0]
|
||||
|
@ -152,8 +152,14 @@ module Reports
|
|||
y = y * 300 / x
|
||||
x = 300
|
||||
end
|
||||
docx.img image_path.split('&')[0] do
|
||||
data asset.blob.download
|
||||
blob_data = if asset_preview.class == ActiveStorage::Preview
|
||||
asset_preview.image.download
|
||||
else
|
||||
asset_preview.blob.download
|
||||
end
|
||||
|
||||
docx.img asset_preview.service_url.split('&')[0] do
|
||||
data blob_data
|
||||
width x
|
||||
height y
|
||||
end
|
||||
|
|
|
@ -121,7 +121,7 @@ module Reports
|
|||
image = TinyMceAsset.find_by(id: Base62.decode(elem.attributes['data-mce-token'].value))
|
||||
return unless image
|
||||
|
||||
image_path = Reports::Utils.image_path(image.image)
|
||||
image_path = Reports::Utils.image_prepare(image).service_url
|
||||
dimension = FastImage.size(image_path)
|
||||
|
||||
return unless dimension
|
||||
|
|
|
@ -6,8 +6,16 @@ module Reports
|
|||
link[0] == '/' ? scinote_url + link : link
|
||||
end
|
||||
|
||||
def self.image_path(attachment)
|
||||
attachment.service_url
|
||||
def self.image_prepare(asset)
|
||||
if asset.class == Asset
|
||||
if asset.inline?
|
||||
asset.large_preview
|
||||
else
|
||||
asset.medium_preview
|
||||
end
|
||||
elsif asset.class == TinyMceAsset
|
||||
asset.image
|
||||
end
|
||||
end
|
||||
|
||||
def self.calculate_color_hsp(color)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WopiUtil
|
||||
require 'open-uri'
|
||||
|
||||
|
@ -5,72 +7,101 @@ module WopiUtil
|
|||
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
|
||||
CLR_TICKS_PER_SECOND = 10000000
|
||||
|
||||
DISCOVERY_TTL = 1.days
|
||||
DISCOVERY_TTL = 1.day
|
||||
DISCOVERY_TTL.freeze
|
||||
|
||||
# For more explanation see this:
|
||||
# http://stackoverflow.com/questions/11888053/
|
||||
# convert-net-datetime-ticks-property-to-date-in-objective-c
|
||||
def convert_to_unix_timestamp(timestamp)
|
||||
Time.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
|
||||
Time.zone.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
|
||||
end
|
||||
|
||||
def get_action(extension, activity)
|
||||
current_wopi_discovery
|
||||
WopiAction.find_action(extension, activity)
|
||||
def get_action(extension, action)
|
||||
discovery = current_wopi_discovery
|
||||
discovery[:actions].find { |i| i[:extension] == extension && i[:action] == action }
|
||||
end
|
||||
|
||||
def current_wopi_discovery
|
||||
discovery = WopiDiscovery.first
|
||||
return discovery if discovery && discovery.expires >= Time.now.to_i
|
||||
initialize_discovery(discovery)
|
||||
initialize_discovery
|
||||
end
|
||||
|
||||
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
|
||||
# with this discovery public key (two key possible old/new)
|
||||
def wopi_verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||
discovery = current_wopi_discovery
|
||||
token_length = [token.length].pack('>N').bytes
|
||||
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
|
||||
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
|
||||
url_length = [url.length].pack('>N').bytes
|
||||
|
||||
expected_proof = token_length + token.bytes +
|
||||
url_length + url.upcase.bytes +
|
||||
timestamp_length + timestamp_bytes
|
||||
|
||||
key = generate_key(discovery[:proof_key_mod], discovery[:proof_key_exp])
|
||||
old_key = generate_key(discovery[:proof_key_old_mod], discovery[:proof_key_old_exp])
|
||||
|
||||
# Try all possible combiniations
|
||||
try_verification(expected_proof, signed_proof, key) ||
|
||||
try_verification(expected_proof, signed_proof_old, key) ||
|
||||
try_verification(expected_proof, signed_proof, old_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
||||
def initialize_discovery(discovery)
|
||||
Rails.logger.warn 'Initializing discovery'
|
||||
discovery.destroy if discovery
|
||||
def initialize_discovery
|
||||
Rails.cache.fetch(:wopi_discovery, expires_in: DISCOVERY_TTL) do
|
||||
@doc = Nokogiri::XML(Kernel.open(ENV['WOPI_DISCOVERY_URL']))
|
||||
discovery_json = {}
|
||||
key = @doc.xpath('//proof-key')
|
||||
discovery_json[:proof_key_mod] = key.xpath('@modulus').first.value
|
||||
discovery_json[:proof_key_exp] = key.xpath('@exponent').first.value
|
||||
discovery_json[:proof_key_old_mod] = key.xpath('@oldmodulus').first.value
|
||||
discovery_json[:proof_key_old_exp] = key.xpath('@oldexponent').first.value
|
||||
discovery_json[:actions] = []
|
||||
|
||||
@doc = Nokogiri::XML(Kernel.open(ENV['WOPI_DISCOVERY_URL']))
|
||||
@doc.xpath('//app').each do |app|
|
||||
app_name = app.xpath('@name').first.value
|
||||
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
|
||||
|
||||
discovery = WopiDiscovery.new
|
||||
discovery.expires = Time.now.to_i + DISCOVERY_TTL
|
||||
key = @doc.xpath('//proof-key')
|
||||
discovery.proof_key_mod = key.xpath('@modulus').first.value
|
||||
discovery.proof_key_exp = key.xpath('@exponent').first.value
|
||||
discovery.proof_key_old_mod = key.xpath('@oldmodulus').first.value
|
||||
discovery.proof_key_old_exp = key.xpath('@oldexponent').first.value
|
||||
discovery.save!
|
||||
icon = app.xpath('@favIconUrl').first.value
|
||||
|
||||
@doc.xpath('//app').each do |app|
|
||||
app_name = app.xpath('@name').first.value
|
||||
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
|
||||
app.xpath('action').each do |action|
|
||||
action_name = action.xpath('@name').first.value
|
||||
next unless %w(view edit editnew embedview wopitest).include?(action_name)
|
||||
|
||||
wopi_app = WopiApp.new
|
||||
wopi_app.name = app.xpath('@name').first.value
|
||||
wopi_app.icon = app.xpath('@favIconUrl').first.value
|
||||
wopi_app.wopi_discovery_id = discovery.id
|
||||
wopi_app.save!
|
||||
app.xpath('action').each do |action|
|
||||
name = action.xpath('@name').first.value
|
||||
next unless %w(view edit editnew wopitest).include?(name)
|
||||
|
||||
wopi_action = WopiAction.new
|
||||
wopi_action.action = name
|
||||
wopi_action.extension = action.xpath('@ext').first.value
|
||||
wopi_action.urlsrc = action.xpath('@urlsrc').first.value
|
||||
wopi_action.wopi_app_id = wopi_app.id
|
||||
wopi_action.save!
|
||||
action_json = {}
|
||||
action_json[:icon] = icon
|
||||
action_json[:action] = action_name
|
||||
action_json[:extension] = action.xpath('@ext').first.value
|
||||
action_json[:urlsrc] = action.xpath('@urlsrc').first.value
|
||||
discovery_json[:actions].push(action_json)
|
||||
end
|
||||
end
|
||||
discovery_json
|
||||
end
|
||||
discovery
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn 'WOPI: initialization failed: ' + e.message
|
||||
e.backtrace.each { |line| Rails.logger.error line }
|
||||
discovery = WopiDiscovery.first
|
||||
discovery.destroy if discovery
|
||||
end
|
||||
|
||||
# Generates a public key from given modulus and exponent
|
||||
def generate_key(modulus, exponent)
|
||||
mod = Base64.decode64(modulus).unpack1('H*').to_i(16)
|
||||
exp = Base64.decode64(exponent).unpack1('H*').to_i(16)
|
||||
|
||||
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
|
||||
OpenSSL::ASN1::Integer.new(exp)])
|
||||
OpenSSL::PKey::RSA.new(seq.to_der)
|
||||
end
|
||||
|
||||
# Verify if decrypting signed_proof with public_key equals to expected_proof
|
||||
def try_verification(expected_proof, signed_proof_b64, public_key)
|
||||
signed_proof = Base64.decode64(signed_proof_b64)
|
||||
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
|
||||
expected_proof.pack('c*'))
|
||||
end
|
||||
|
||||
def create_wopi_file_activity(current_user, started_editing)
|
||||
|
|
19
app/views/assets/_asset.html.erb
Normal file
19
app/views/assets/_asset.html.erb
Normal file
|
@ -0,0 +1,19 @@
|
|||
<%
|
||||
gallery_view_id = nil unless defined? gallery_view_id
|
||||
deletable = asset.step.present?
|
||||
editable = true
|
||||
|
||||
partial_locals = {
|
||||
asset: asset,
|
||||
gallery_view_id: gallery_view_id,
|
||||
deletable: deletable,
|
||||
editable: editable
|
||||
}
|
||||
%>
|
||||
<% if asset.inline? %>
|
||||
<%= render partial: 'assets/asset_inline.html.erb', locals: partial_locals %>
|
||||
<% elsif asset.list? %>
|
||||
<%= render partial: 'assets/asset_list.html.erb', locals: partial_locals %>
|
||||
<% else %>
|
||||
<%= render partial: 'assets/asset_thumbnail.html.erb', locals: partial_locals %>
|
||||
<% end %>
|
70
app/views/assets/_asset_context_menu.html.erb
Normal file
70
app/views/assets/_asset_context_menu.html.erb
Normal file
|
@ -0,0 +1,70 @@
|
|||
<div class="dropdown asset-context-menu">
|
||||
<button class="btn btn-light dropdown-toggle icon-btn" type="button" id="dropdownAssetContextMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="dropdownAssetContextMenu"
|
||||
data-asset-id="<%= asset.id %>"
|
||||
data-toggle-view-url="<%= toggle_view_mode_path(asset) %>">
|
||||
<% if can_manage_asset?(asset) && editable %>
|
||||
<% if wopi_enabled? && wopi_file?(asset) %>
|
||||
<li>
|
||||
<% edit_supported, title = wopi_file_edit_button_status(asset) %>
|
||||
<%= render partial: 'assets/wopi/file_wopi_controls.html.erb',
|
||||
locals: {
|
||||
asset: asset,
|
||||
edit_supported: edit_supported,
|
||||
title: title
|
||||
} %>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<% elsif asset.file.metadata[:asset_type] == 'marvinjs' %>
|
||||
<li>
|
||||
<a class="btn btn-light marvinjs-edit-button"
|
||||
data-sketch-id="<%= asset.id %>"
|
||||
data-update-url="<%= marvin_js_asset_path(asset) %>"
|
||||
data-sketch-start-edit-url="<%= start_editing_marvin_js_asset_path(asset) %>"
|
||||
data-sketch-name="<%= asset.file.metadata[:name] %>"
|
||||
data-sketch-description="<%= asset.file.metadata[:description] %>"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
<%= t('assets.file_preview.edit_in_marvinjs') %>
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<% elsif asset.editable_image? %>
|
||||
<li>
|
||||
<a class="btn btn-light image-edit-button"
|
||||
data-image-id="<%= asset.id %>"
|
||||
data-image-name="<%= asset.file_name %>"
|
||||
data-image-url="<%= asset_file_url_path(asset) %>"
|
||||
data-image-quality="<%= asset.file_image_quality || 80 %>"
|
||||
data-image-mime-type="<%= asset.file.content_type %>"
|
||||
data-image-start-edit-url="<%= start_edit_image_path(asset) %>"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
<%= t('assets.file_preview.edit_in_scinote') %>
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li class="divider-label"><%= t("assets.context_menu.set_view_size") %></li>
|
||||
<% ['inline', 'thumbnail', 'list'].each do |view_mode| %>
|
||||
<li>
|
||||
<a class="change-preview-type <%= 'selected' if view_mode == asset.view_mode %>" data-preview-type="<%= view_mode %>">
|
||||
<%= t("assets.context_menu.#{view_mode}_html") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can_manage_asset?(asset) && deletable %>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a class="delete-asset" href="<%= asset_destroy_path(asset) %>">
|
||||
<i class="fas fa-trash"></i>
|
||||
<%= t("assets.context_menu.delete") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
45
app/views/assets/_asset_inline.html.erb
Normal file
45
app/views/assets/_asset_inline.html.erb
Normal file
|
@ -0,0 +1,45 @@
|
|||
<div class="inline-attachment-container asset"
|
||||
data-asset-id="<%= asset.id %>"
|
||||
data-asset-file-name="<%= asset.file_name %>"
|
||||
data-asset-updated-at="<%= asset.updated_at.to_i %>"
|
||||
data-asset-order="0"
|
||||
>
|
||||
<div class="header">
|
||||
<div class="file-info">
|
||||
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
|
||||
class: "file-preview-link file-name",
|
||||
id: "modal_link#{asset.id}",
|
||||
data: {
|
||||
no_turbolink: true,
|
||||
id: asset.id,
|
||||
gallery_view_id: gallery_view_id,
|
||||
preview_url: asset_file_preview_path(asset)
|
||||
} do %>
|
||||
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
|
||||
<%= asset.file.metadata['name'] %>
|
||||
<% else %>
|
||||
<%= asset.file_name %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="file-metadata">
|
||||
<span><%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %></span>
|
||||
<span><%= t('assets.placeholder.size_label', size: number_to_human_size(asset.file_size)) %></span>
|
||||
</div>
|
||||
</div>
|
||||
<%= render partial: 'assets/asset_context_menu.html.erb', locals: { asset: asset, deletable: deletable, editable: editable } %>
|
||||
</div>
|
||||
<% if wopi_enabled? && wopi_file?(asset) %>
|
||||
<div class="iframe-placeholder" data-iframe-url="<%= asset.get_action_url(current_user, 'embedview') %>"></div>
|
||||
<% elsif asset.previewable? %>
|
||||
<div class="image-container">
|
||||
<%= image_tag asset.large_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="general-file-container">
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
30
app/views/assets/_asset_list.html.erb
Normal file
30
app/views/assets/_asset_list.html.erb
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class="list-attachment-container asset"
|
||||
data-asset-id="<%= asset.id %>"
|
||||
data-asset-file-name="<%= asset.file_name %>"
|
||||
data-asset-updated-at="<%= asset.updated_at.to_i %>"
|
||||
data-asset-order="1"
|
||||
>
|
||||
<div class="file-icon">
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
</div>
|
||||
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
|
||||
class: "file-preview-link file-name",
|
||||
id: "modal_link#{asset.id}",
|
||||
data: {
|
||||
no_turbolink: true,
|
||||
id: asset.id,
|
||||
gallery_view_id: gallery_view_id,
|
||||
preview_url: asset_file_preview_path(asset)
|
||||
} do %>
|
||||
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
|
||||
<%= asset.file.metadata['name'] %>
|
||||
<% else %>
|
||||
<%= asset.file_name %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="file-metadata">
|
||||
<span><%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %></span>
|
||||
<span><%= t('assets.placeholder.size_label', size: number_to_human_size(asset.file_size)) %></span>
|
||||
</div>
|
||||
<%= render partial: 'assets/asset_context_menu.html.erb', locals: { asset: asset, deletable: deletable, editable: editable } %>
|
||||
</div>
|
39
app/views/assets/_asset_thumbnail.html.erb
Normal file
39
app/views/assets/_asset_thumbnail.html.erb
Normal file
|
@ -0,0 +1,39 @@
|
|||
<div class="attachment-container asset"
|
||||
data-asset-id="<%= asset.id %>"
|
||||
data-asset-file-name="<%= asset.file_name %>"
|
||||
data-asset-updated-at="<%= asset.updated_at.to_i %>"
|
||||
data-asset-order="2"
|
||||
>
|
||||
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
|
||||
class: "file-preview-link",
|
||||
id: "modal_link#{asset.id}",
|
||||
data: {
|
||||
no_turbolink: true,
|
||||
id: asset.id,
|
||||
gallery_view_id: gallery_view_id,
|
||||
preview_url: asset_file_preview_path(asset)
|
||||
} do %>
|
||||
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
||||
<% if asset.previewable? %>
|
||||
<%= image_tag asset.medium_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="attachment-label">
|
||||
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
|
||||
<%= asset.file.metadata['name'] %>
|
||||
<% else %>
|
||||
<%= asset.file_name %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="attachment-metadata">
|
||||
<%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %><br>
|
||||
<%= number_to_human_size(asset.file_size) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render partial: 'assets/asset_context_menu.html.erb', locals: { asset: asset, deletable: deletable, editable: editable } %>
|
||||
</div>
|
|
@ -1,9 +1,9 @@
|
|||
<div class="attachment-placeholder pull-left new">
|
||||
<div class="attachment-thumbnail no-shadow">
|
||||
<div class="attachment-container pull-left new">
|
||||
<div class="attachment-preview no-shadow">
|
||||
<i class="fas fa-image"></i>
|
||||
</div>
|
||||
<div class="attachment-label"><%= truncate(file_name || file_url, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</div>
|
||||
<div class="spencer-bonnet-modif">
|
||||
<div class="attachment-metadata">
|
||||
</div>
|
||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||
<button
|
||||
class="btn btn-light new-marvinjs-upload-button"
|
||||
data-object-id="<%= element_id %>"
|
||||
data-object-type="<%= element_type %>"
|
||||
data-marvin-url="<%= marvin_js_assets_path %>"
|
||||
data-sketch-container="<%= sketch_container %>"
|
||||
>
|
||||
<span class="new-marvinjs-upload-icon">
|
||||
<%= image_tag 'icon_small/marvinjs.svg' %>
|
||||
</span>
|
||||
<%= t('marvinjs.new_button') %>
|
||||
</button>
|
|
@ -1,12 +1,14 @@
|
|||
<li
|
||||
class="new-marvinjs-upload-button"
|
||||
data-object-id="<%= element_id %>"
|
||||
data-object-type="<%= element_type %>"
|
||||
data-marvin-url="<%= marvin_js_assets_path %>"
|
||||
data-sketch-container="<%= sketch_container %>"
|
||||
>
|
||||
<span class="new-marvinjs-upload-icon">
|
||||
<%= image_tag 'icon_small/marvinjs.svg' %>
|
||||
</span>
|
||||
<%= t('marvinjs.new_li_button') %>
|
||||
<li>
|
||||
<a
|
||||
class="new-marvinjs-upload-button"
|
||||
data-object-id="<%= element_id %>"
|
||||
data-object-type="<%= element_type %>"
|
||||
data-marvin-url="<%= marvin_js_assets_path %>"
|
||||
data-sketch-container="<%= sketch_container %>"
|
||||
>
|
||||
<span class="new-marvinjs-upload-icon">
|
||||
<%= image_tag 'icon_small/marvinjs.svg' %>
|
||||
</span>
|
||||
<%= t('marvinjs.new_li_button') %>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<% if wopi_enabled? %>
|
||||
<%= link_to create_wopi_file_path,
|
||||
class: 'btn btn-light create-wopi-file-btn',
|
||||
target: '_blank',
|
||||
title: 'Create_new_file',
|
||||
data: { 'id': element_id, 'type': element_type, } do %>
|
||||
<span class="new-asset-upload-button">
|
||||
<%= image_tag 'office/office.svg' %>
|
||||
<%=t 'assets.create_wopi_file.button_text' %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
|
@ -1,28 +1,18 @@
|
|||
<%= link_to view_asset_url(id: asset),
|
||||
id: 'wopi_file_view_button',
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do %>
|
||||
<%= file_application_icon(asset) %>
|
||||
<%= wopi_button_text(asset, 'view') %>
|
||||
<% end %>
|
||||
<% if can_edit && edit_supported %>
|
||||
<% if edit_supported %>
|
||||
<%= link_to edit_asset_url(id: asset),
|
||||
id: 'wopi_file_edit_button',
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do %>
|
||||
class: 'btn btn-light',
|
||||
target: '_blank' do %>
|
||||
<%= file_application_icon(asset) %>
|
||||
<%= wopi_button_text(asset, 'edit') %>
|
||||
<% end %>
|
||||
<% elsif can_edit %>
|
||||
<% else %>
|
||||
<%= link_to edit_asset_url(id: asset),
|
||||
id: 'wopi_file_edit_button',
|
||||
class: 'btn btn-default btn-sm',
|
||||
class: 'btn btn-light',
|
||||
target: '_blank',
|
||||
title: title,
|
||||
disabled: true,
|
||||
style: 'display: inline-block' do %>
|
||||
disabled: true do %>
|
||||
<%= file_application_icon(asset) %>
|
||||
<%= wopi_button_text(asset, 'edit') %>
|
||||
<% end %>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
<!-- About us modal -->
|
||||
<%= render "shared/about_modal" %>
|
||||
<%= render "shared/file_preview_modal.html.erb" %>
|
||||
<%= render "shared/file_preview/modal.html.erb" %>
|
||||
<%= render "shared/file_edit_modal.html.erb" %>
|
||||
<%= render "shared/marvinjs_modal.html.erb" %>
|
||||
<%= render "shared/navigation" %>
|
||||
|
|
|
@ -3,6 +3,5 @@
|
|||
<% elsif result.is_table %>
|
||||
<%= render partial: "results/result_table.html.erb", locals: {result: result} %>
|
||||
<% elsif result.is_asset %>
|
||||
<%= render partial: 'steps/attachments/item.html.erb',
|
||||
locals: { asset: result.asset, i: 0, assets_count: 0, step: nil, order_atoz: 0, order_ztoa: 0 } %>
|
||||
<%= render partial: 'assets/asset.html.erb', locals: { asset: result.asset, gallery_view_id: result.my_module.id } %>
|
||||
<% end %>
|
||||
|
|
|
@ -17,5 +17,3 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>FilePreviewModal.init()</script>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<% result ||= @result %>
|
||||
<% asset = result.asset %>
|
||||
<% is_image = result.asset.image? %>
|
||||
<% comments = result.result_comments %>
|
||||
<% timestamp = asset.created_at %>
|
||||
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
|
||||
<% icon_class = 'fas ' + file_fa_icon_class(asset) if asset.file_name %>
|
||||
<div class="report-element report-result-element report-result-asset-element"
|
||||
data-ts="<%= timestamp.to_i %>"
|
||||
data-type="result_asset"
|
||||
|
@ -29,8 +28,13 @@
|
|||
<em><%= t("projects.reports.elements.result_asset.file_name", file: filename) %></em>
|
||||
</a>
|
||||
<% else %>
|
||||
<em><%= t("projects.reports.elements.result_asset.file_name",
|
||||
file: truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH)) %></em>
|
||||
<em>
|
||||
<%= t("projects.reports.elements.result_asset.file_name",
|
||||
file: truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH)) %>
|
||||
<a href="<%= asset_download_url(asset) %>" class="download-link">
|
||||
<%= t('projects.reports.elements.download') %>
|
||||
</a>
|
||||
</em>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pull-left user-time">
|
||||
|
@ -42,7 +46,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row"></div>
|
||||
<% if is_image %>
|
||||
<% if asset.previewable? && !asset.list? %>
|
||||
<div class="report-element-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 file-image">
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
<% asset ||= @asset %>
|
||||
<% is_image = asset.image? %>
|
||||
<% timestamp = asset.created_at %>
|
||||
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
|
||||
<% icon_class = file_fa_icon_class(asset) if asset.file_name %>
|
||||
<div class="report-element report-step-attachment-element report-step-asset-element" data-ts="<%= timestamp.to_i %>" data-type="step_asset" data-id='{ "asset_id": <%= asset.id %> }' data-scroll-id="<%= asset.id %>" data-name="<%=t "projects.reports.elements.step_asset.sidebar_name", file: asset.file_name %>" data-icon-class="<%= icon_class %>">
|
||||
<div class="report-element-header">
|
||||
<div class="row">
|
||||
<div class="pull-left attachment-icon">
|
||||
<span class="<%= icon_class %>"></span>
|
||||
<span class="fas <%= icon_class %>"></span>
|
||||
</div>
|
||||
<div class="pull-left file-name">
|
||||
<% if defined? export_all and export_all %>
|
||||
<a href="<%= path %>">
|
||||
<em><%=t 'projects.reports.elements.step_asset.file_name',
|
||||
file: filename %></em>
|
||||
<em><%= t('projects.reports.elements.step_asset.file_name', file: filename) %></em>
|
||||
</a>
|
||||
<% else %>
|
||||
<em><%=t 'projects.reports.elements.step_asset.file_name',
|
||||
file: truncate(asset.file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></em>
|
||||
<em>
|
||||
<%= t('projects.reports.elements.step_asset.file_name',
|
||||
file: truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH)) %>
|
||||
<a href="<%= asset_download_url(asset) %>" class="download-link">
|
||||
<%= t('projects.reports.elements.download') %>
|
||||
</a>
|
||||
</em>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pull-left user-time">
|
||||
|
@ -29,7 +32,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="report-element-body">
|
||||
<% if is_image %>
|
||||
<% if asset.previewable? && !asset.list? %>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 file-image">
|
||||
<% if defined?(export_all) && export_all %>
|
||||
|
|
|
@ -93,7 +93,6 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<script>FilePreviewModal.init()</script>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t('general.close')%></button>
|
||||
</div>
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="preview-close" data-dismiss="modal"><span class="fas fa-times"></span></button>
|
||||
<span class="file-name"></span>
|
||||
<a class="file-save-link" href='#'>
|
||||
<p><span class="fas fa-save"></span> <%= t('SaveClose')%></p>
|
||||
</a>
|
||||
<div class="sci-btn-group">
|
||||
<a class="file-save-link btn btn-light" href='#'>
|
||||
<span class="fas fa-save"></span> <%= t('SaveClose')%>
|
||||
</a>
|
||||
<button type="button" class="preview-close btn btn-light icon-btn" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="tui-image-editor">
|
||||
|
|
105
app/views/shared/file_preview/_content.html.erb
Normal file
105
app/views/shared/file_preview/_content.html.erb
Normal file
|
@ -0,0 +1,105 @@
|
|||
<div class="modal-header">
|
||||
<span class="file-name"><%= asset.file_name %></span>
|
||||
<div class="sci-btn-group">
|
||||
<% if can_edit %>
|
||||
<% if wopi_enabled? && wopi_file?(asset) %>
|
||||
<% edit_supported, title = wopi_file_edit_button_status(asset) %>
|
||||
<%= render partial: 'assets/wopi/file_wopi_controls.html.erb',
|
||||
locals: {
|
||||
asset: asset,
|
||||
edit_supported: edit_supported,
|
||||
title: title
|
||||
} %>
|
||||
<% elsif asset.file.metadata[:asset_type] == 'marvinjs' %>
|
||||
<button class="btn btn-light marvinjs-edit-button"
|
||||
data-sketch-id="<%= asset.id %>"
|
||||
data-update-url="<%= marvin_js_asset_path(asset) %>"
|
||||
data-sketch-start-edit-url="<%= start_editing_marvin_js_asset_path(asset) %>"
|
||||
data-sketch-name="<%= asset.file.metadata[:name] %>"
|
||||
data-sketch-description="<%= asset.file.metadata[:description] %>"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
<%= t('assets.file_preview.edit_in_marvinjs') %>
|
||||
</button>
|
||||
<% elsif asset.editable_image? %>
|
||||
<button class="btn btn-light image-edit-button"
|
||||
data-image-id="<%= asset.id %>"
|
||||
data-image-name="<%= asset.file_name %>"
|
||||
data-image-url="<%= asset_file_url_path(asset) %>"
|
||||
data-image-quality="<%= asset.file_image_quality || 80 %>"
|
||||
data-image-mime-type="<%= asset.file.content_type %>"
|
||||
data-image-start-edit-url="<%= start_edit_image_path(asset) %>"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
<%= t('assets.file_preview.edit_in_scinote') %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<a class="btn btn-light file-download-link" href="<%= rails_blob_path(asset.file, disposition: 'attachment') %>" data-turbolinks="false">
|
||||
<span class="fas fa-download"></span> <%= t('Download')%>
|
||||
</a>
|
||||
<% if asset.step || asset.result %>
|
||||
<%= render partial: 'assets/asset_context_menu.html.erb', locals: { asset: asset, deletable: false, editable: false } %>
|
||||
<% end %>
|
||||
<button type="button" class="btn icon-btn btn-light" data-dismiss="modal"><span class="fas fa-times"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="file-preview-container">
|
||||
<% if asset.previewable? %>
|
||||
<% if wopi_enabled? && wopi_file?(asset) %>
|
||||
<iframe class="wopi-file-preview" src="<%= asset.get_action_url(current_user, 'embedview') %>"></iframe>
|
||||
<% else %>
|
||||
<%= image_tag asset.large_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= render partial: 'shared/file_preview/file_icon.html.erb', locals: { asset: asset } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<% if gallery %>
|
||||
<div class="gallery">
|
||||
<% previous_asset = Asset.find_by(id: gallery.split(asset.id.to_s)[0]&.last) %>
|
||||
<% next_asset = Asset.find_by(id: gallery.split(asset.id.to_s)[1]&.first) %>
|
||||
<% if previous_asset && can_read_asset?(previous_asset) %>
|
||||
<%= link_to asset_file_preview_path(previous_asset),
|
||||
class: "previous-asset gallery-switcher",
|
||||
data: {id: previous_asset.id, gallery_elements: gallery} do %>
|
||||
<% if previous_asset.previewable? %>
|
||||
<%= image_tag previous_asset.medium_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(previous_asset) if previous_asset.file_name %>"></i>
|
||||
<% end %>
|
||||
<i class="fas fa-angle-left"></i>
|
||||
<span class="file-name"><%= previous_asset.file_name %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="file-counter">
|
||||
<%= gallery.split(asset.id.to_s)[0].length + 1 %> of <%= gallery.length %> attachments
|
||||
</span>
|
||||
<% if next_asset && can_read_asset?(next_asset) %>
|
||||
<%= link_to asset_file_preview_path(next_asset),
|
||||
class: "next-asset gallery-switcher",
|
||||
data: {id: next_asset.id, gallery_elements: gallery} do %>
|
||||
<span class="file-name"><%= next_asset.file_name %></span>
|
||||
<i class="fas fa-angle-right"></i>
|
||||
<% if next_asset.previewable? %>
|
||||
<%= image_tag next_asset.medium_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(next_asset) if next_asset.file_name %>"></i>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
4
app/views/shared/file_preview/_file_icon.html.erb
Normal file
4
app/views/shared/file_preview/_file_icon.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<i class="fas fa-10x <%= file_fa_icon_class(asset) %>"></i>
|
||||
<h3 class="file-name"></h3>
|
||||
</div>
|
13
app/views/shared/file_preview/_modal.html.erb
Normal file
13
app/views/shared/file_preview/_modal.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div id="filePreviewModal"
|
||||
class="modal modal-file-preview"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
aria-labelledby="filePreviewModal"
|
||||
aria-hidden="true"
|
||||
data-backdrop="static"
|
||||
data-keyboard="false">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -72,8 +72,8 @@
|
|||
<div id="new-step-assets-group" class="form-group">
|
||||
<div class="col-xs-12 attachments edit">
|
||||
<%= f.nested_fields_for :assets do |ff| %>
|
||||
<%= render partial: 'shared/asset_placeholder.html.erb',
|
||||
locals: { edit_page: true, asset: ff.object, ff: ff } %>
|
||||
<%= render partial: 'steps/attachments/edit_item.html.erb',
|
||||
locals: { asset: ff.object, ff: ff } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<div class="attachment-placeholder pull-left">
|
||||
|
||||
<div class="attachment-thumbnail <%= asset.previewable? ? '' : 'no-shadow' %> <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
||||
<div class="attachment-container step-asset" data-asset-id="<%= asset.id %>">
|
||||
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
||||
<% if asset.previewable? %>
|
||||
<%= image_tag asset.medium_preview,
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
|
||||
onload: 'ActiveStoragePreviews.showPreview(event)',
|
||||
style: 'opacity: 0' %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
<% end %>
|
||||
|
@ -17,17 +16,14 @@
|
|||
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="spencer-bonnet-modif">
|
||||
<%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %> <br>
|
||||
<div class="attachment-metadata">
|
||||
<%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %><br>
|
||||
<%= number_to_human_size(asset.file_size) %>
|
||||
</div>
|
||||
|
||||
<% if edit_page %>
|
||||
<% unless asset.locked? %>
|
||||
<div class="remove-icon pull-right">
|
||||
<% unless ff.object.file.attached? && ff.object.locked? %>
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="fas fa-trash"></span>
|
||||
<% end %>
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="fas fa-trash"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
<div class="pseudo-attachment-container" style="order: <%= i %>">
|
||||
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
|
||||
class: 'file-preview-link',
|
||||
id: "modal_link#{asset.id}",
|
||||
data: { no_turbolink: true,
|
||||
id: true,
|
||||
'preview-url': asset_file_preview_path(asset),
|
||||
'order-atoz': order_atoz,
|
||||
'order-ztoa': order_ztoa,
|
||||
'order-new': i,
|
||||
'order-old': assets_count - i,
|
||||
} do %>
|
||||
|
||||
<%= render partial: 'shared/asset_placeholder.html.erb', locals: { edit_page: false, asset: asset } %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,4 +1,5 @@
|
|||
<% assets = ordered_assets(step) %>
|
||||
<% assets = step.assets %>
|
||||
<% current_order = step.current_view_state(current_user).state.dig('assets', 'sort') %>
|
||||
<div class="col-xs-12">
|
||||
<hr>
|
||||
</div>
|
||||
|
@ -10,42 +11,55 @@
|
|||
</h4>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="attachemnts-header pull-right">
|
||||
<% if !(preview) && (can_manage_protocol_in_module?(@protocol) ||
|
||||
<div class="actions">
|
||||
<div class="dropdown sci-dropdown">
|
||||
<button class="btn btn-light dropdown-toggle" type="button" id="dropdownAttachmentsOptions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span><%= t("protocols.steps.attachments.manage") %></span>
|
||||
<span class="caret pull-right"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right dropdown-attachment-options"
|
||||
aria-labelledby="dropdownAttachmentsOptions"
|
||||
data-view-mode-url="<%= update_asset_view_mode_step_path(step) %>"
|
||||
data-step-id="<%= step.id %>"
|
||||
data-state-save-url="<%= update_view_state_step_path(step.id) %>">
|
||||
<% if !(preview) && (can_manage_protocol_in_module?(@protocol) ||
|
||||
can_manage_protocol_in_repository?(@protocol)) %>
|
||||
<%= render partial: '/assets/marvinjs/create_marvin_sketch_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attachments#att-#{step.id}" } %>
|
||||
<%= render partial: '/assets/wopi/create_wopi_file_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step' } %>
|
||||
<% end %>
|
||||
<div class="dropdown attachments-order" id="dd-att-step-<%= step.id %>">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span id="dd-att-step-<%= step.id %>-label">
|
||||
<%= t("general.sort.#{step.current_view_state(current_user).state.dig('assets', 'sort')}_html") %>
|
||||
</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="sortMenu" data-state-save-path="<%= update_view_state_step_path(step.id) %>">
|
||||
<% ['new', 'old', 'atoz', 'ztoa'].each do |sort| %>
|
||||
<li>
|
||||
<a data-order="<%= sort %>" onClick="reorderAttachments(this, '<%= step.id %>', '<%= sort %>')">
|
||||
<%= t("general.sort.#{sort}_html") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<li class="divider-label"><%= t("protocols.steps.attachments.add") %></li>
|
||||
<li>
|
||||
<%= render partial: '/assets/marvinjs/create_marvin_sketch_li.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attachments[data-step-id=#{step.id}]" } %>
|
||||
</li>
|
||||
<li>
|
||||
<%= render partial: '/assets/wopi/create_wopi_file_li.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step' } %>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<% end %>
|
||||
<li class="divider-label"><%= t("protocols.steps.attachments.sort_by") %></li>
|
||||
<% ['new', 'old', 'atoz', 'ztoa'].each do |sort| %>
|
||||
<li>
|
||||
<a data-order="<%= sort %>" class="action-link change-order <%= 'selected' if current_order == sort %>">
|
||||
<%= t("general.sort_new.#{sort}") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="divider-label"><%= t("protocols.steps.attachments.attachments_view_mode") %></li>
|
||||
<% ['inline', 'thumbnail', 'list'].each do |view_mode| %>
|
||||
<li>
|
||||
<a class="attachments-view-mode action-link <%= 'selected' if view_mode == step.assets_view_mode %>" data-assets-view-mode="<%= view_mode %>">
|
||||
<%= t("protocols.steps.attachments.view_mode.#{view_mode}_html") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 attachments" id="att-<%= step.id %>">
|
||||
<div class="col-xs-12 attachments" data-step-id="<%= step.id %>" data-order="<%= current_order %>">
|
||||
<% assets.each_with_index do |asset, i| %>
|
||||
<% order_atoz = az_ordered_assets_index(step, asset.id) %>
|
||||
<% order_ztoa = assets.length - az_ordered_assets_index(step, asset.id) %>
|
||||
<%= render partial: 'steps/attachments/item.html.erb',
|
||||
locals: { asset: asset, i: i, assets_count: assets.length, step: step, order_atoz: order_atoz, order_ztoa: order_ztoa } %>
|
||||
<%= render partial: 'assets/asset.html.erb', locals: { asset: asset, gallery_view_id: step.id } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
|
@ -444,6 +444,7 @@ en:
|
|||
appended_image: "Appended image - %{name}"
|
||||
appended_table: "Appended table"
|
||||
elements:
|
||||
download: "[Download]"
|
||||
modals:
|
||||
project_contents:
|
||||
head_title: "Add contents to report"
|
||||
|
@ -1957,11 +1958,19 @@ en:
|
|||
uncomplete_title: "Restart step"
|
||||
attachments:
|
||||
modified_label: "Modified:"
|
||||
manage: "Manage"
|
||||
add: "ADD"
|
||||
sort_by: "SORT BY"
|
||||
attachments_view_mode: "ALL ATTACHMENTS VIEW SIZE"
|
||||
sort:
|
||||
new_html: "Newest first ↓"
|
||||
old_html: "Oldest first ↑"
|
||||
atoz_html: "Name ↓"
|
||||
ztoa_html: "Name ↑"
|
||||
view_mode:
|
||||
inline_html: "<i class=\"fas fa-desktop\"></i> Extra large"
|
||||
thumbnail_html: "<i class=\"fas fa-th-large\"></i> Medium"
|
||||
list_html: "<i class=\"fas fa-list\"></i> List"
|
||||
new:
|
||||
add_step_title: "Add new step"
|
||||
tab_checklists: "Checklists"
|
||||
|
@ -2079,6 +2088,7 @@ en:
|
|||
task_completed: "%{user} completed task %{module}. %{date} | Project: %{project} | Experiment: %{experiment}"
|
||||
|
||||
assets:
|
||||
file_deleted: "File \"%{file_name}\" deleted"
|
||||
head_title:
|
||||
edit: "SciNote | %{file_name} | Edit"
|
||||
view: "SciNote | %{file_name} | View"
|
||||
|
@ -2095,6 +2105,7 @@ en:
|
|||
file_name_placeholder: 'Image'
|
||||
placeholder:
|
||||
modified_label: "Modified:"
|
||||
size_label: "Size: %{size}"
|
||||
wopi_supported_text_formats_title: 'Only .docx, .docm, .odt file formats are supported for editing in Word Online.'
|
||||
wopi_supported_table_formats_title: 'Only .xlsx, .xlsm, .xlsb, .ods file formats are supported for editing in Excel Online.'
|
||||
wopi_supported_presentation_formats_title: 'Only .pptx, ppsx, .odp file formats are supported for editing in Powerpoint Online.'
|
||||
|
@ -2111,6 +2122,16 @@ en:
|
|||
errors:
|
||||
forbidden: 'You do not have permission to add files.'
|
||||
not_found: 'Element not found.'
|
||||
file_preview:
|
||||
edit_in_scinote: "Edit in SciNote"
|
||||
edit_in_marvinjs: "Edit in Marvin JS"
|
||||
context_menu:
|
||||
set_view_size: "SET VIEW SIZE"
|
||||
delete: "Delete file"
|
||||
inline_html: "<i class=\"fas fa-desktop\"></i> Extra large"
|
||||
thumbnail_html: "<i class=\"fas fa-th-large\"></i> Medium"
|
||||
list_html: "<i class=\"fas fa-list\"></i> List"
|
||||
|
||||
atwho:
|
||||
no_results:
|
||||
projects: "Projects with this name were not found"
|
||||
|
|
|
@ -413,6 +413,7 @@ Rails.application.routes.draw do
|
|||
put 'move_down'
|
||||
put 'move_up'
|
||||
post 'update_view_state'
|
||||
post 'update_asset_view_mode'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -599,8 +600,10 @@ Rails.application.routes.draw do
|
|||
get 'files/:id/file_url', to: 'assets#file_url', as: 'asset_file_url'
|
||||
get 'files/:id/download', to: 'assets#download', as: 'asset_download'
|
||||
get 'files/:id/edit', to: 'assets#edit', as: 'edit_asset'
|
||||
patch 'files/:id/toggle_view_mode', to: 'assets#toggle_view_mode', as: 'toggle_view_mode'
|
||||
post 'files/:id/update_image', to: 'assets#update_image',
|
||||
as: 'update_asset_image'
|
||||
delete 'files/:id/', to: 'assets#destroy', as: 'asset_destroy'
|
||||
post 'files/create_wopi_file',
|
||||
to: 'assets#create_wopi_file',
|
||||
as: 'create_wopi_file'
|
||||
|
|
5
db/migrate/20200902093234_add_view_mode_to_asset.rb
Normal file
5
db/migrate/20200902093234_add_view_mode_to_asset.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddViewModeToAsset < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :assets, :view_mode, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAssetsViewModeToStep < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :steps, :assets_view_mode, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
|
@ -224,7 +224,8 @@ CREATE TABLE public.assets (
|
|||
version integer DEFAULT 1,
|
||||
file_processing boolean,
|
||||
team_id integer,
|
||||
file_image_quality integer
|
||||
file_image_quality integer,
|
||||
view_mode integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -2069,7 +2070,8 @@ CREATE TABLE public.steps (
|
|||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
last_modified_by_id bigint,
|
||||
protocol_id bigint NOT NULL
|
||||
protocol_id bigint NOT NULL,
|
||||
assets_view_mode integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -6960,6 +6962,8 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20200713142353'),
|
||||
('20200714082503'),
|
||||
('20200826143431'),
|
||||
('20200909121441');
|
||||
('20200902093234'),
|
||||
('20200909121441'),
|
||||
('20201027133634');
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue