mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-21 15:36:22 +08:00
Merge pull request #1710 from aignatov-bio/feature/marvinjs-integration
MarvinJS integration
This commit is contained in:
commit
3cbabf1ffd
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -74,3 +74,8 @@ spec/addons
|
|||
|
||||
# RVM/rbenv ruby version for local development
|
||||
.ruby-version
|
||||
|
||||
#ignore marvinJs
|
||||
|
||||
public/marvinjs
|
||||
public/marvin4js-license.cxl
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
//= require select2_customization
|
||||
//= require shared/inline_editing
|
||||
//= require turbolinks
|
||||
//= require marvinjslauncher
|
||||
|
||||
|
||||
// Initialize links for submitting forms. This is useful for submitting
|
||||
|
|
320
app/assets/javascripts/marvinjs_editor.js
Normal file
320
app/assets/javascripts/marvinjs_editor.js
Normal file
|
@ -0,0 +1,320 @@
|
|||
/* global MarvinJSUtil, I18n, FilePreviewModal, tinymce */
|
||||
/* global TinyMCE, PerfectScrollbar, ChemicalizeMarvinJs */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable wrap-iife */
|
||||
var MarvinJsEditor;
|
||||
if (typeof ChemicalizeMarvinJs !== 'undefined') {
|
||||
ChemicalizeMarvinJs.createEditor('#marvinjs-sketch');
|
||||
}
|
||||
MarvinJsEditor = (function() {
|
||||
var marvinJsModal = $('#MarvinJsModal');
|
||||
var marvinJsContainer = $('#marvinjs-editor');
|
||||
var marvinJsObject = $('#marvinjs-sketch');
|
||||
var emptySketch = '<cml><MDocument></MDocument></cml>';
|
||||
var sketchName = marvinJsModal.find('.file-name input');
|
||||
|
||||
var loadEditor = () => {
|
||||
if (marvinJsObject[0].nodeName === 'DIV') {
|
||||
return MarvinJSUtil.getEditor('#' + marvinJsObject.children()[0].id);
|
||||
}
|
||||
return MarvinJSUtil.getEditor('#marvinjs-sketch');
|
||||
};
|
||||
|
||||
var loadPackages = () => {
|
||||
if (marvinJsObject[0].nodeName === 'DIV') {
|
||||
return MarvinJSUtil.getPackage('#' + marvinJsObject.children()[0].id);
|
||||
}
|
||||
return MarvinJSUtil.getPackage('#marvinjs-sketch');
|
||||
};
|
||||
|
||||
function preloadActions(config) {
|
||||
if (config.mode === 'new' || config.mode === 'new-tinymce') {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.importStructure('mrv', emptySketch);
|
||||
sketchName.val(I18n.t('marvinjs.new_sketch'));
|
||||
});
|
||||
} else if (config.mode === 'edit') {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.importStructure('mrv', config.data);
|
||||
sketchName.val(config.name);
|
||||
});
|
||||
} else if (config.mode === 'edit-tinymce') {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
$.get(config.marvinUrl, function(result) {
|
||||
sketcherInstance.importStructure('mrv', result.description);
|
||||
sketchName.val(result.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createExporter(marvin, imageType) {
|
||||
var inputFormat = 'mrv';
|
||||
var settings = {
|
||||
width: 900,
|
||||
height: 900
|
||||
};
|
||||
|
||||
var params = {
|
||||
imageType: imageType,
|
||||
settings: settings,
|
||||
inputFormat: inputFormat
|
||||
};
|
||||
return new marvin.ImageExporter(params);
|
||||
}
|
||||
|
||||
function assignImage(target, data) {
|
||||
target.attr('src', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function TinyMceBuildHTML(json) {
|
||||
var imgstr = "<img src='" + json.image.url + "'";
|
||||
imgstr += " data-mce-token='" + json.image.token + "'";
|
||||
imgstr += " data-source-id='" + json.image.source_id + "'";
|
||||
imgstr += " data-source-type='" + json.image.source_type + "'";
|
||||
imgstr += " alt='description-" + json.image.token + "' />";
|
||||
return imgstr;
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
open: function(config) {
|
||||
MarvinJsEditor().team_sketches();
|
||||
preloadActions(config);
|
||||
$(marvinJsModal).modal('show');
|
||||
$(marvinJsObject)
|
||||
.css('width', (marvinJsContainer.width() - 200) + 'px')
|
||||
.css('height', marvinJsContainer.height() + 'px');
|
||||
marvinJsModal.find('.file-save-link').off('click').on('click', () => {
|
||||
if (config.mode === 'new') {
|
||||
MarvinJsEditor().save(config);
|
||||
} else if (config.mode === 'edit') {
|
||||
MarvinJsEditor().update(config);
|
||||
} else if (config.mode === 'new-tinymce') {
|
||||
config.objectType = 'TinyMceAsset';
|
||||
MarvinJsEditor().save_with_image(config);
|
||||
} else if (config.mode === 'edit-tinymce') {
|
||||
MarvinJsEditor().update_tinymce(config);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
initNewButton: function(selector) {
|
||||
$(selector).off('click').on('click', function() {
|
||||
var objectId = this.dataset.objectId;
|
||||
var objectType = this.dataset.objectType;
|
||||
var marvinUrl = this.dataset.marvinUrl;
|
||||
var container = this.dataset.sketchContainer;
|
||||
MarvinJsEditor().open({
|
||||
mode: 'new',
|
||||
objectId: objectId,
|
||||
objectType: objectType,
|
||||
marvinUrl: marvinUrl,
|
||||
container: container
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
save: function(config) {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.exportStructure('mrv').then(function(source) {
|
||||
$.post(config.marvinUrl, {
|
||||
description: source,
|
||||
object_id: config.objectId,
|
||||
object_type: config.objectType,
|
||||
name: sketchName.val()
|
||||
}, function(result) {
|
||||
var newAsset;
|
||||
if (config.objectType === 'Step') {
|
||||
newAsset = $(result.html);
|
||||
newAsset.find('.file-preview-link').css('top', '-300px');
|
||||
newAsset.addClass('new').prependTo($(config.container));
|
||||
setTimeout(function() {
|
||||
newAsset.find('.file-preview-link').css('top', '0px');
|
||||
}, 200);
|
||||
FilePreviewModal.init();
|
||||
}
|
||||
$(marvinJsModal).modal('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
save_with_image: function(config) {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.exportStructure('mrv').then(function(mrvDescription) {
|
||||
loadPackages().then(function(sketcherPackage) {
|
||||
sketcherPackage.onReady(function() {
|
||||
var exporter = createExporter(sketcherPackage, 'image/jpeg');
|
||||
exporter.render(mrvDescription).then(function(image) {
|
||||
$.post(config.marvinUrl, {
|
||||
description: mrvDescription,
|
||||
object_id: config.objectId,
|
||||
object_type: config.objectType,
|
||||
name: sketchName.val(),
|
||||
image: image
|
||||
}, function(result) {
|
||||
var json = tinymce.util.JSON.parse(result);
|
||||
config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json));
|
||||
TinyMCE.updateImages(config.editor);
|
||||
$(marvinJsModal).modal('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
update: function(config) {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.exportStructure('mrv').then(function(source) {
|
||||
$.ajax({
|
||||
url: config.marvinUrl,
|
||||
data: {
|
||||
description: source,
|
||||
name: sketchName.val()
|
||||
},
|
||||
dataType: 'json',
|
||||
type: 'PUT',
|
||||
success: function(json) {
|
||||
$(marvinJsModal).modal('hide');
|
||||
config.reloadImage.src.val(json.description);
|
||||
$(config.reloadImage.sketch).find('.attachment-label').text(json.name);
|
||||
MarvinJsEditor().create_preview(
|
||||
config.reloadImage.src,
|
||||
$(config.reloadImage.sketch).find('img')
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
update_tinymce: function(config) {
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.exportStructure('mrv').then(function(mrvDescription) {
|
||||
loadPackages().then(function(sketcherPackage) {
|
||||
sketcherPackage.onReady(function() {
|
||||
var exporter = createExporter(sketcherPackage, 'image/jpeg');
|
||||
exporter.render(mrvDescription).then(function(image) {
|
||||
$.ajax({
|
||||
url: config.marvinUrl,
|
||||
data: {
|
||||
description: mrvDescription,
|
||||
name: sketchName.val(),
|
||||
object_type: 'TinyMceAsset',
|
||||
image: image
|
||||
},
|
||||
dataType: 'json',
|
||||
type: 'PUT',
|
||||
success: function(json) {
|
||||
config.image[0].src = json.url;
|
||||
$(marvinJsModal).modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
create_preview: function(source, target) {
|
||||
loadPackages().then(function(sketcherInstance) {
|
||||
sketcherInstance.onReady(function() {
|
||||
var exporter = createExporter(sketcherInstance, 'image/jpeg');
|
||||
var sketchConfig = source.val();
|
||||
exporter.render(sketchConfig).then(function(result) {
|
||||
assignImage(target, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
create_download_link: function(source, link, filename) {
|
||||
loadPackages().then(function(sketcherInstance) {
|
||||
sketcherInstance.onReady(function() {
|
||||
var exporter = createExporter(sketcherInstance, 'image/jpeg');
|
||||
var sketchConfig = source.val();
|
||||
exporter.render(sketchConfig).then(function(result) {
|
||||
link.attr('href', result);
|
||||
link.attr('download', filename);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
delete_sketch: function(url, object) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
type: 'DELETE',
|
||||
success: function() {
|
||||
$(object).remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
team_sketches: function() {
|
||||
var ps = new PerfectScrollbar(marvinJsContainer.find('.marvinjs-team-sketch')[0]);
|
||||
marvinJsContainer.find('.sketch-container').remove();
|
||||
$.get('/marvin_js_assets/team_sketches', function(result) {
|
||||
$(result.html).appendTo(marvinJsContainer.find('.marvinjs-team-sketch'));
|
||||
|
||||
$.each(result.sketches, function(i, sketch) {
|
||||
var sketchObj = marvinJsContainer.find('.marvinjs-team-sketch .sketch-container[data-sketch-id="' + sketch + '"]');
|
||||
var src = sketchObj.find('#description');
|
||||
var dest = sketchObj.find('img');
|
||||
MarvinJsEditor().create_preview(src, dest);
|
||||
setTimeout(() => { ps.update(); }, 500);
|
||||
marvinJsContainer.find('.sketch-container').click(function() {
|
||||
var sketchContainer = $(this);
|
||||
loadEditor().then(function(sketcherInstance) {
|
||||
sketcherInstance.importStructure('mrv', sketchContainer.find('#description').val());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
tinymce.PluginManager.requireLangPack('MarvinJsPlugin');
|
||||
|
||||
tinymce.create('tinymce.plugins.MarvinJsPlugin', {
|
||||
MarvinJsPlugin: function(ed) {
|
||||
var editor = ed;
|
||||
|
||||
function openMarvinJs() {
|
||||
MarvinJsEditor().open({
|
||||
mode: 'new-tinymce',
|
||||
marvinUrl: '/marvin_js_assets',
|
||||
editor: editor
|
||||
});
|
||||
}
|
||||
// Add a button that opens a window
|
||||
editor.addButton('marvinjsplugin', {
|
||||
tooltip: I18n.t('marvinjs.new_button'),
|
||||
icon: 'file-invoice',
|
||||
onclick: openMarvinJs
|
||||
});
|
||||
|
||||
// Adds a menu item to the tools menu
|
||||
editor.addMenuItem('marvinjsplugin', {
|
||||
text: I18n.t('marvinjs.new_button'),
|
||||
icon: 'file-invoice',
|
||||
context: 'insert',
|
||||
onclick: openMarvinJs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tinymce.PluginManager.add(
|
||||
'marvinjsplugin',
|
||||
tinymce.plugins.MarvinJsPlugin
|
||||
);
|
||||
})();
|
|
@ -374,7 +374,8 @@
|
|||
}
|
||||
|
||||
function initCallBacks() {
|
||||
applyCreateWopiFileCallback();
|
||||
applyCreateWopiFileCallback()
|
||||
if (typeof(MarvinJsEditor) !== 'undefined') MarvinJsEditor().initNewButton('.new-marvinjs-upload-button');
|
||||
applyCheckboxCallBack();
|
||||
applyStepCompletedCallBack();
|
||||
applyEditCallBack();
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* 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 setupAssetsLoading I18n PerfectScrollbar*/
|
||||
/* global Uint8Array fabric tui animateSpinner
|
||||
setupAssetsLoading I18n PerfectScrollbar MarvinJsEditor */
|
||||
//= require assets
|
||||
|
||||
var FilePreviewModal = (function() {
|
||||
|
@ -18,10 +19,15 @@ var FilePreviewModal = (function() {
|
|||
$('.file-preview-link').off('click');
|
||||
$('.file-preview-link').click(function(e) {
|
||||
e.preventDefault();
|
||||
name = $(this).find('p').text();
|
||||
name = $(this).find('.attachment-label').text();
|
||||
url = $(this).data('preview-url');
|
||||
downloadUrl = $(this).attr('href');
|
||||
if ($(this).data('asset-type') === 'marvin-sketch') {
|
||||
openMarvinPrevieModal(name, $(this).find('#description'), this);
|
||||
return true;
|
||||
}
|
||||
openPreviewModal(name, url, downloadUrl);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -388,23 +394,39 @@ var FilePreviewModal = (function() {
|
|||
|
||||
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);
|
||||
setupAssetsLoading();
|
||||
}
|
||||
}).done(function() {
|
||||
|
||||
function closeEditor() {
|
||||
animateSpinner(null, false);
|
||||
imageEditor.destroy();
|
||||
imageEditor = {};
|
||||
$('#tui-image-editor').html('');
|
||||
$('#fileEditModal').modal('hide');
|
||||
});
|
||||
}
|
||||
|
||||
if (data.mode === 'tinymce') {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: data.url,
|
||||
data: dataUpload,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(res) {
|
||||
data.image.src = res.url;
|
||||
}
|
||||
}).done(function() { closeEditor(); });
|
||||
} else {
|
||||
$.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);
|
||||
setupAssetsLoading();
|
||||
}
|
||||
}).done(function() { closeEditor(); });
|
||||
}
|
||||
});
|
||||
|
||||
window.onresize = function() {
|
||||
|
@ -421,8 +443,7 @@ var FilePreviewModal = (function() {
|
|||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var link = modal.find('.file-download-link');
|
||||
modal.find('.file-preview-container').empty();
|
||||
modal.find('.file-wopi-controls').empty();
|
||||
clearPrevieModal();
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'wopi-controls')) {
|
||||
modal.find('.file-wopi-controls').html(data['wopi-controls']);
|
||||
}
|
||||
|
@ -520,7 +541,52 @@ var FilePreviewModal = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
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 openMarvinPrevieModal(name, src, sketch) {
|
||||
var modal = $('#filePreviewModal');
|
||||
var link = modal.find('.file-download-link');
|
||||
var target;
|
||||
clearPrevieModal();
|
||||
|
||||
modal.modal('show');
|
||||
modal.find('.file-preview-container')
|
||||
.append($('<img>').attr('src', '').attr('alt', ''));
|
||||
target = modal.find('.file-preview-container').find('img');
|
||||
MarvinJsEditor().create_preview(src, target);
|
||||
MarvinJsEditor().create_download_link(src, link, name);
|
||||
modal.find('.file-name').text(name);
|
||||
$.get(sketch.dataset.updateUrl, function(result) {
|
||||
if (!readOnly && result.editable) {
|
||||
modal.find('.file-edit-link').css('display', '');
|
||||
modal.find('.file-edit-link').off().click(function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
modal.modal('hide');
|
||||
MarvinJsEditor().open({
|
||||
mode: 'edit',
|
||||
data: src.val(),
|
||||
name: name,
|
||||
marvinUrl: sketch.dataset.updateUrl,
|
||||
reloadImage: {
|
||||
src: src,
|
||||
sketch: sketch
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
modal.find('.file-edit-link').css('display', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
init: initPreviewModal
|
||||
init: initPreviewModal,
|
||||
imageEditor: initImageEditor
|
||||
});
|
||||
}(window));
|
||||
|
|
98
app/assets/javascripts/sitewide/tiny_mce.js
vendored
98
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -1,4 +1,4 @@
|
|||
/* global _ hljs tinyMCE SmartAnnotation */
|
||||
/* global _ hljs tinyMCE SmartAnnotation MarvinJsEditor FilePreviewModal */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var TinyMCE = (function() {
|
||||
|
@ -37,11 +37,84 @@ var TinyMCE = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
function initImageToolBar(editor) {
|
||||
var editorForm = $(editor.getContainer()).closest('form');
|
||||
var editorContainer = $(editor.getContainer());
|
||||
var menuBar = editorForm.find('.mce-menubar.mce-toolbar.mce-first .mce-flow-layout');
|
||||
var editorToolbar = editorForm.find('.mce-top-part');
|
||||
var editorIframe = $('#' + editor.id).prev().find('.mce-edit-area iframe');
|
||||
$('<div class="tinymce-active-object-handler" style="display:none">'
|
||||
+ '<a class="file-download-link tool-button" href="#" data-turbolinks="false"><i class="mce-ico mce-i-donwload"></i></a>'
|
||||
+ '<span class="file-edit-link tool-button" href="#" data-turbolinks="false"><i class="mce-ico mce-i-pencil"></i></span>'
|
||||
+ '<span class="file-image-editor-link tool-button" href="#" data-turbolinks="false"><i class="mce-ico mce-i-image"></i></span>'
|
||||
+ '</div>').appendTo(editorToolbar.find('.mce-stack-layout'));
|
||||
editorIframe.contents().click(function() {
|
||||
var marvinJsEdit;
|
||||
setTimeout(() => {
|
||||
var image = editorIframe.contents().find('img[data-mce-selected="1"]');
|
||||
var editLink;
|
||||
var imageEditorLink;
|
||||
if (image.length > 0) {
|
||||
image.on('load', function() {
|
||||
editor.fire('Dirty');
|
||||
});
|
||||
editorContainer.find('.tinymce-active-object-handler').css('display', 'block');
|
||||
editorContainer.find('.tinymce-active-object-handler .file-download-link')
|
||||
.attr('href', image[0].src)
|
||||
.attr('download', 'tinymce-image');
|
||||
|
||||
// Edit link
|
||||
editLink = editorContainer.find('.tinymce-active-object-handler .file-edit-link');
|
||||
if (image[0].dataset.sourceId) {
|
||||
editLink.css('display', 'inline-block');
|
||||
marvinJsEdit = (image[0].dataset.sourceType === 'MarvinJsAsset' && typeof (MarvinJsEditor) !== 'undefined');
|
||||
if (!marvinJsEdit) editLink.css('display', 'none');
|
||||
editLink.on('click', function() {
|
||||
if (marvinJsEdit) {
|
||||
MarvinJsEditor().open({
|
||||
mode: 'edit-tinymce',
|
||||
marvinUrl: '/marvin_js_assets/' + image[0].dataset.sourceId,
|
||||
image: image
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
editLink.css('display', 'none');
|
||||
editLink.off('click');
|
||||
}
|
||||
|
||||
// imaged editor Link
|
||||
imageEditorLink = editorContainer.find('.tinymce-active-object-handler .file-image-editor-link');
|
||||
if (image[0].dataset.mceToken && image[0].dataset.sourceId) {
|
||||
imageEditorLink.css('display', 'inline-block');
|
||||
imageEditorLink.on('click', function() {
|
||||
FilePreviewModal.imageEditor({
|
||||
'download-url': image[0].src,
|
||||
filename: 'tinymce-image.jpg',
|
||||
mode: 'tinymce',
|
||||
url: '/tiny_mce_assets/' + image[0].dataset.mceToken,
|
||||
quality: 100,
|
||||
'mime-type': 'image/jpeg',
|
||||
image: image[0]
|
||||
});
|
||||
});
|
||||
} else {
|
||||
imageEditorLink.css('display', 'none');
|
||||
imageEditorLink.off('click');
|
||||
}
|
||||
} else {
|
||||
editorContainer.find('.tinymce-active-object-handler').css('display', 'none');
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// returns a public API for TinyMCE editor
|
||||
return Object.freeze({
|
||||
init: function(selector, mceConfig = {}) {
|
||||
var tinyMceContainer;
|
||||
var tinyMceInitSize;
|
||||
var plugins;
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
// Hide element containing HTML view of RTE field
|
||||
tinyMceContainer = $(selector).closest('form').find('.tinymce-view');
|
||||
|
@ -49,14 +122,14 @@ var TinyMCE = (function() {
|
|||
$(selector).closest('.form-group')
|
||||
.before('<div class="tinymce-placeholder" style="height:' + tinyMceInitSize + 'px"></div>');
|
||||
tinyMceContainer.addClass('hidden');
|
||||
|
||||
|
||||
plugins = 'autosave autoresize customimageuploader link advlist codesample autolink lists charmap hr anchor searchreplace wordcount visualblocks visualchars insertdatetime nonbreaking save directionality paste textcolor colorpicker textpattern';
|
||||
if (typeof (MarvinJsEditor) !== 'undefined') plugins += ' marvinjsplugin';
|
||||
tinyMCE.init({
|
||||
cache_suffix: '?v=4.9.3', // This suffix should be changed any time library is updated
|
||||
selector: selector,
|
||||
menubar: 'file edit view insert format',
|
||||
toolbar: 'undo redo restoredraft | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | forecolor backcolor | customimageuploader | codesample',
|
||||
plugins: 'autosave autoresize customimageuploader link advlist codesample autolink lists charmap hr anchor searchreplace wordcount visualblocks visualchars insertdatetime nonbreaking save directionality paste textcolor colorpicker textpattern',
|
||||
toolbar: 'undo redo restoredraft | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | forecolor backcolor | customimageuploader marvinjsplugin | codesample',
|
||||
plugins: plugins,
|
||||
codesample_languages: [
|
||||
{ text: 'R', value: 'r' },
|
||||
{ text: 'MATLAB', value: 'matlab' },
|
||||
|
@ -129,9 +202,11 @@ var TinyMCE = (function() {
|
|||
],
|
||||
init_instance_callback: function(editor) {
|
||||
var editorForm = $(editor.getContainer()).closest('form');
|
||||
var editorContainer = $(editor.getContainer());
|
||||
var menuBar = editorForm.find('.mce-menubar.mce-toolbar.mce-first .mce-flow-layout');
|
||||
var editorToolbar = editorForm.find('.mce-top-part');
|
||||
var editorToolbaroffset = mceConfig.toolbar_offset || 120;
|
||||
var editorIframe = $('#' + editor.id).prev().find('.mce-edit-area iframe');
|
||||
|
||||
$('.tinymce-placeholder').css('height', $(editor.editorContainer).height() + 'px');
|
||||
setTimeout(() => {
|
||||
|
@ -149,6 +224,9 @@ var TinyMCE = (function() {
|
|||
moveToolbar(editor, editorToolbar, editorToolbaroffset);
|
||||
});
|
||||
|
||||
// Init image toolbar
|
||||
initImageToolBar(editor);
|
||||
|
||||
|
||||
// Update scroll position after exit
|
||||
function updateScrollPosition() {
|
||||
|
@ -254,6 +332,16 @@ var TinyMCE = (function() {
|
|||
getContent: function() {
|
||||
return tinyMCE.editors[0].getContent();
|
||||
},
|
||||
updateImages(editor) {
|
||||
var images;
|
||||
var iframe = $('#' + editor.id).prev().find('.mce-edit-area iframe').contents();
|
||||
images = $.map($('img', iframe), e => {
|
||||
return e.dataset.mceToken;
|
||||
});
|
||||
$('#' + editor.id).next()[0].value = JSON.stringify(images);
|
||||
return JSON.stringify(images);
|
||||
},
|
||||
|
||||
highlight: initHighlightjs
|
||||
});
|
||||
}());
|
||||
|
|
|
@ -504,6 +504,6 @@ $md-color-shadow-light: rgba(0, 0, 0, .12);
|
|||
$md-color-shadow-dark: rgba(0, 0, 0, .24);
|
||||
|
||||
$md-shadow: 0 1px 3px $md-color-shadow-light, 0 1px 2px $md-color-shadow-dark;
|
||||
$md-shadow-hover: 0 14px 28px $md-color-shadow-dark, 0 10px 10px $md-color-shadow-dark;
|
||||
$md-shadow-hover: 0 12px 12px $md-color-shadow-dark, 0 10px 10px $md-color-shadow-dark;
|
||||
|
||||
$md-transaction: all .4s cubic-bezier(.25, .8, .25, 1);
|
||||
|
|
162
app/assets/stylesheets/marvinjs.scss
Normal file
162
app/assets/stylesheets/marvinjs.scss
Normal file
|
@ -0,0 +1,162 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
// scss-lint:disable ImportantRule
|
||||
// scss-lint:disable IdSelector
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
// MarvinJs modal
|
||||
.modal-marvin-js {
|
||||
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;
|
||||
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;
|
||||
|
||||
input {
|
||||
border-radius: 5px;
|
||||
box-shadow: none;
|
||||
color: $color-black;
|
||||
height: 40px;
|
||||
outline: 0;
|
||||
padding: 5px 10px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: calc(100% - 60px);
|
||||
padding: 0;
|
||||
|
||||
#marvinjs-editor {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
#marvinjs-sketch {
|
||||
border-right: 1px solid $color-gainsboro;
|
||||
float: left;
|
||||
min-height: 450px;
|
||||
min-width: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marvinjs-team-sketch {
|
||||
background: $color-white;
|
||||
float: right;
|
||||
height: calc(100% - 40px);
|
||||
overflow-y: scroll;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.marvinjs-team-sketch-header {
|
||||
background: $color-white;
|
||||
border-bottom: 1px solid $color-gainsboro;
|
||||
color: $color-emperor;
|
||||
float: right;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
line-height: 39px;
|
||||
text-align: center;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sketch-container {
|
||||
@include md-card-style;
|
||||
cursor: pointer;
|
||||
margin: 10px;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
|
||||
.sketch-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sketch-name {
|
||||
color: $brand-primary;
|
||||
font-family: Lato;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
margin: 10px auto;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.sketch-object {
|
||||
color: $color-emperor;
|
||||
font-size: 12px;
|
||||
opacity: .6;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-save-link {
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#new-step-sketch {
|
||||
|
||||
.sketch-container {
|
||||
display: grid;
|
||||
float: left;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mce-i-file-invoice::before {
|
||||
content: "\F570";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
}
|
|
@ -15,7 +15,9 @@
|
|||
list-style: none;
|
||||
|
||||
li {
|
||||
& > div > span.pull-left {
|
||||
margin-bottom: 10px;
|
||||
|
||||
> div > span.pull-left {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +94,35 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +130,7 @@
|
|||
@include md-card-style;
|
||||
color: $color-silver-chalice;
|
||||
height: 280px;
|
||||
margin: 8px;
|
||||
margin: 4px 8px 16px;
|
||||
text-align: center;
|
||||
width: 220px;
|
||||
|
||||
|
@ -167,6 +198,7 @@
|
|||
|
||||
.remove-icon {
|
||||
bottom: 15px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
position: relative;
|
||||
right: 10px;
|
||||
|
@ -245,8 +277,8 @@
|
|||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
|
|
|
@ -59,4 +59,50 @@
|
|||
.mce-toolbar {
|
||||
background: $color-white !important;
|
||||
}
|
||||
// scss-lint:enable ImportantRule
|
||||
|
||||
.mce-stack-layout {
|
||||
.tinymce-active-object-handler {
|
||||
border-top: 1px solid rgb(226, 228, 231);
|
||||
height: 33px;
|
||||
width: 100%;
|
||||
|
||||
.tool-button {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 27px;
|
||||
margin: 2px;
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid rgb(226, 228, 231);
|
||||
}
|
||||
}
|
||||
|
||||
.mce-i-donwload::before {
|
||||
content: "\F019";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.mce-i-pencil::before {
|
||||
content: "\F303";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.mce-i-image::before {
|
||||
content: "\F03E";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
// scss-lint:enable ImportantRule
|
||||
|
|
84
app/controllers/marvin_js_assets_controller.rb
Normal file
84
app/controllers/marvin_js_assets_controller.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MarvinJsAssetsController < ApplicationController
|
||||
def create
|
||||
new_asset = MarvinJsAsset.add_sketch(marvin_params, current_team)
|
||||
if new_asset.object_type == 'Step'
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'assets/marvinjs/marvin_sketch_card.html.erb',
|
||||
locals: { sketch: new_asset, i: 0, assets_count: 0, step: new_asset.object }
|
||||
)
|
||||
}
|
||||
elsif new_asset.object_type == 'TinyMceAsset'
|
||||
tiny_img = TinyMceAsset.find(new_asset.object_id)
|
||||
render json: {
|
||||
image: {
|
||||
url: view_context.image_url(tiny_img.url(:large)),
|
||||
token: Base62.encode(tiny_img.id),
|
||||
source_id: new_asset.id,
|
||||
source_type: new_asset.class.name
|
||||
}
|
||||
}, content_type: 'text/html'
|
||||
elsif new_asset
|
||||
render json: new_asset
|
||||
else
|
||||
render json: new_asset.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
sketch = current_team.marvin_js_assets.find_by_id(params[:id])
|
||||
if sketch
|
||||
if sketch.object_type == 'Step'
|
||||
editable = can_manage_protocol_in_module?(sketch.object.protocol) ||
|
||||
can_manage_protocol_in_repository?(sketch.object.protocol)
|
||||
render json: {
|
||||
sketch: sketch,
|
||||
editable: editable
|
||||
}
|
||||
else
|
||||
render json: sketch
|
||||
end
|
||||
else
|
||||
render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
sketch = current_team.marvin_js_assets.find_by_id(params[:id])
|
||||
if sketch.destroy
|
||||
render json: sketch
|
||||
else
|
||||
render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
sketch = MarvinJsAsset.update_sketch(marvin_params, current_team)
|
||||
if sketch
|
||||
render json: sketch
|
||||
else
|
||||
render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def team_sketches
|
||||
result = ''
|
||||
sketches = current_team.marvin_js_assets.where.not(object_type: 'TinyMceAsset')
|
||||
sketches.each do |sketch|
|
||||
result += render_to_string(
|
||||
partial: 'shared/marvinjs_modal_sketch.html.erb',
|
||||
locals: { sketch: sketch }
|
||||
)
|
||||
end
|
||||
|
||||
render json: { html: result, sketches: sketches.pluck(:id) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def marvin_params
|
||||
params.permit(:id, :description, :object_id, :object_type, :name, :image)
|
||||
end
|
||||
end
|
|
@ -595,7 +595,11 @@ class StepsController < ApplicationController
|
|||
:name,
|
||||
:contents,
|
||||
:_destroy
|
||||
]
|
||||
],
|
||||
marvin_js_assets_attributes: %i(
|
||||
id
|
||||
_destroy
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TinyMceAssetsController < ApplicationController
|
||||
|
||||
def create
|
||||
image = params.fetch(:file) { render_404 }
|
||||
tiny_img = TinyMceAsset.new(image: image,
|
||||
|
@ -21,4 +20,9 @@ class TinyMceAssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def update
|
||||
image = TinyMceAsset.find_by_id(Base62.decode(params[:id]))
|
||||
image.update(image: params[:image], image_file_name: image.image_file_name)
|
||||
render json: { url: view_context.image_url(image.url) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MyModulesHelper
|
||||
def ordered_step_of(my_module)
|
||||
my_module.protocol.steps.order(:position)
|
||||
|
@ -8,11 +10,21 @@ module MyModulesHelper
|
|||
end
|
||||
|
||||
def ordered_assets(step)
|
||||
step.assets.order(:file_updated_at)
|
||||
assets = []
|
||||
assets += step.assets
|
||||
assets += step.marvin_js_assets if MarvinJsAsset.enabled?
|
||||
assets.sort! do |a, b|
|
||||
a[asset_date_sort_field(a)] <=> b[asset_date_sort_field(b)]
|
||||
end
|
||||
end
|
||||
|
||||
def az_ordered_assets_index(step, asset_id)
|
||||
step.assets.order('LOWER(file_file_name)').pluck(:id).index(asset_id)
|
||||
assets = []
|
||||
assets += step.assets
|
||||
assets += step.marvin_js_assets if MarvinJsAsset.enabled?
|
||||
assets.sort! do |a, b|
|
||||
(a[asset_name_sort_field(a)] || '').downcase <=> (b[asset_name_sort_field(b)] || '').downcase
|
||||
end.pluck(:id).index(asset_id)
|
||||
end
|
||||
|
||||
def number_of_samples(my_module)
|
||||
|
@ -35,11 +47,28 @@ module MyModulesHelper
|
|||
end
|
||||
|
||||
def is_steps_page?
|
||||
action_name == "steps"
|
||||
action_name == 'steps'
|
||||
end
|
||||
|
||||
def is_results_page?
|
||||
action_name == "results"
|
||||
action_name == 'results'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def asset_date_sort_field(element)
|
||||
result = {
|
||||
'Asset' => :file_updated_at,
|
||||
'MarvinJsAsset' => :updated_at
|
||||
}
|
||||
result[element.class.name]
|
||||
end
|
||||
|
||||
def asset_name_sort_field(element)
|
||||
result = {
|
||||
'Asset' => :file_file_name,
|
||||
'MarvinJsAsset' => :name
|
||||
}
|
||||
result[element.class.name]
|
||||
end
|
||||
end
|
||||
|
|
52
app/models/marvin_js_asset.rb
Normal file
52
app/models/marvin_js_asset.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MarvinJsAsset < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
validates :object_id, presence: true
|
||||
validates :object_type, presence: true
|
||||
|
||||
belongs_to :object, polymorphic: true,
|
||||
optional: true,
|
||||
inverse_of: :marvin_js_assets
|
||||
|
||||
belongs_to :team, inverse_of: :marvin_js_assets, optional: true
|
||||
|
||||
def self.url
|
||||
ENV['MARVINJS_URL']
|
||||
end
|
||||
|
||||
def self.enabled?
|
||||
ENV['MARVINJS_URL'] != nil || ENV['MARVINJS_API_KEY'] != nil
|
||||
end
|
||||
|
||||
def self.add_sketch(values, team)
|
||||
if values[:object_type] == 'TinyMceAsset'
|
||||
tiny_mce_img = TinyMceAsset.create(
|
||||
object: nil,
|
||||
team_id: team.id,
|
||||
saved: false,
|
||||
image: values[:image],
|
||||
image_file_name: "#{name}.jpg"
|
||||
)
|
||||
values[:object_id] = tiny_mce_img.id
|
||||
end
|
||||
values[:name] = I18n.t('marvinjs.new_sketch') if values[:name].empty?
|
||||
create(values.merge(team_id: team.id).except(:image))
|
||||
end
|
||||
|
||||
def self.update_sketch(values, team)
|
||||
sketch = team.marvin_js_assets.find(values[:id])
|
||||
return false unless sketch
|
||||
|
||||
values[:name] = I18n.t('marvinjs.new_sketch') if values[:name].empty?
|
||||
sketch.update(values.except(:image, :object_type, :id))
|
||||
|
||||
if values[:object_type] == 'TinyMceAsset'
|
||||
image = TinyMceAsset.find(sketch.object_id)
|
||||
image.update(image: values[:image], image_file_name: "#{name}.jpg")
|
||||
return { url: image.url(:large), description: sketch.description }
|
||||
end
|
||||
sketch
|
||||
end
|
||||
end
|
|
@ -33,6 +33,11 @@ class Step < ApplicationRecord
|
|||
has_many :report_elements, inverse_of: :step,
|
||||
dependent: :destroy
|
||||
|
||||
has_many :marvin_js_assets,
|
||||
as: :object,
|
||||
class_name: :MarvinJsAsset,
|
||||
dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :checklists,
|
||||
reject_if: :all_blank,
|
||||
allow_destroy: true
|
||||
|
@ -44,6 +49,9 @@ class Step < ApplicationRecord
|
|||
attributes['contents'].blank?
|
||||
},
|
||||
allow_destroy: true
|
||||
accepts_nested_attributes_for :marvin_js_assets,
|
||||
reject_if: :all_blank,
|
||||
allow_destroy: true
|
||||
|
||||
after_destroy :cascade_after_destroy
|
||||
before_save :set_last_modified_by
|
||||
|
|
|
@ -39,6 +39,7 @@ class Team < ApplicationRecord
|
|||
has_many :repositories, dependent: :destroy
|
||||
has_many :reports, inverse_of: :team, dependent: :destroy
|
||||
has_many :activities, inverse_of: :team, dependent: :destroy
|
||||
has_many :marvin_js_assets, inverse_of: :team, dependent: :destroy
|
||||
|
||||
attr_accessor :without_templates
|
||||
attr_accessor :without_intro_demo
|
||||
|
|
|
@ -14,6 +14,11 @@ class TinyMceAsset < ApplicationRecord
|
|||
touch: true,
|
||||
optional: true
|
||||
|
||||
has_one :marvin_js_asset,
|
||||
as: :object,
|
||||
class_name: :MarvinJsAsset,
|
||||
dependent: :destroy
|
||||
|
||||
belongs_to :object, polymorphic: true,
|
||||
optional: true,
|
||||
inverse_of: :tiny_mce_assets
|
||||
|
@ -33,6 +38,10 @@ class TinyMceAsset < ApplicationRecord
|
|||
}
|
||||
validates :estimated_size, presence: true
|
||||
|
||||
def source
|
||||
return marvin_js_asset if marvin_js_asset
|
||||
end
|
||||
|
||||
def self.update_images(object, images)
|
||||
images = JSON.parse(images)
|
||||
current_images = object.tiny_mce_assets.pluck(:id)
|
||||
|
@ -54,10 +63,15 @@ class TinyMceAsset < ApplicationRecord
|
|||
tm_assets.each do |tm_asset|
|
||||
asset_id = tm_asset.attr('data-mce-token')
|
||||
new_asset_url = find_by_id(Base62.decode(asset_id))
|
||||
if new_asset_url
|
||||
tm_asset.attributes['src'].value = new_asset_url.url
|
||||
tm_asset['class'] = 'img-responsive'
|
||||
next unless new_asset_url
|
||||
|
||||
assets_source = new_asset_url.source
|
||||
if assets_source
|
||||
tm_asset.set_attribute('data-source-id', assets_source.id)
|
||||
tm_asset.set_attribute('data-source-type', assets_source.class.name)
|
||||
end
|
||||
tm_asset.attributes['src'].value = new_asset_url.url
|
||||
tm_asset['class'] = 'img-responsive'
|
||||
end
|
||||
description.css('body').inner_html.to_s
|
||||
end
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<span
|
||||
class="btn btn-default 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="fas fa-file-invoice new-marvinjs-upload-icon"></span>
|
||||
<%= t('marvinjs.new_button') %>
|
||||
</span>
|
18
app/views/assets/marvinjs/_marvin_sketch_card.html.erb
Normal file
18
app/views/assets/marvinjs/_marvin_sketch_card.html.erb
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="pseudo-attachment-container" style="order: <%= assets_count - i %>">
|
||||
<%= link_to '',
|
||||
class: 'file-preview-link',
|
||||
id: "marvin_js_sketch_#{sketch.id}",
|
||||
data: { no_turbolink: true, id: true, status: 'asset-present',
|
||||
'preview-url': '',
|
||||
'order-atoz': az_ordered_assets_index(step, sketch.id),
|
||||
'order-ztoa': assets_count - az_ordered_assets_index(step, sketch.id),
|
||||
'order-old': i,
|
||||
'order-new': assets_count - i,
|
||||
'asset-type': 'marvin-sketch',
|
||||
'asset-id': sketch.id,
|
||||
'update_url': marvin_js_asset_path(sketch.id)
|
||||
} do %>
|
||||
<%= render partial: 'assets/marvinjs/marvin_sketch_card_placeholder.html.erb',
|
||||
locals: { edit_page: false, sketch: sketch } %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
<div class="attachment-placeholder pull-left" data-marvinjs-sketch="<%= sketch.id %>">
|
||||
<div class="attachment-thumbnail">
|
||||
<img src>
|
||||
<%= hidden_field_tag :description, sketch.description %>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
src=$('.attachment-placeholder[data-marvinjs-sketch="<%= sketch.id %>"]').find('#description')
|
||||
target=$('.attachment-placeholder[data-marvinjs-sketch="<%= sketch.id %>"]').find('img')
|
||||
MarvinJsEditor().create_preview(src,target)
|
||||
})()
|
||||
</script>
|
||||
<div class="attachment-label"><%= truncate(sketch.name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></div>
|
||||
<div class="spencer-bonnet-modif">
|
||||
<%= t('protocols.steps.attachments.modified_label') %> <%= l(sketch.updated_at, format: :full_date) if sketch.updated_at %>
|
||||
</div>
|
||||
<% if edit_page %>
|
||||
<div class="remove-icon pull-right">
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="fas fa-trash"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
|
@ -7,6 +7,11 @@
|
|||
<%= stylesheet_link_tag 'application', media: 'all' %>
|
||||
<%= javascript_include_tag 'application' %>
|
||||
|
||||
<% if ENV['MARVINJS_API_KEY'] %>
|
||||
<script src="https://marvinjs.chemicalize.com/v1/<%= ENV['MARVINJS_API_KEY'] %>/client-settings.js"></script>
|
||||
<script src="https://marvinjs.chemicalize.com/v1/client.js"></script>
|
||||
<% end %>
|
||||
|
||||
<%= favicon_link_tag "favicon.ico" %>
|
||||
<%= favicon_link_tag "favicon-16.png", type: "image/png", size: "16x16" %>
|
||||
<%= favicon_link_tag "favicon-32.png", type: "image/png", size: "32x32" %>
|
||||
|
@ -44,6 +49,9 @@
|
|||
<%= render "shared/about_modal" %>
|
||||
<%= render "shared/file_preview_modal.html.erb" %>
|
||||
<%= render "shared/file_edit_modal.html.erb" %>
|
||||
<% if MarvinJsAsset.enabled? %>
|
||||
<%= render "shared/marvinjs_modal.html.erb" %>
|
||||
<% end %>
|
||||
<%= render "shared/navigation" %>
|
||||
|
||||
<% if user_signed_in? && flash[:system_notification_modal] && current_user.show_login_system_notification? %>
|
||||
|
|
31
app/views/shared/_marvinjs_modal.html.erb
Normal file
31
app/views/shared/_marvinjs_modal.html.erb
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div id="MarvinJsModal"
|
||||
class="modal modal-marvin-js"
|
||||
role="dialog"
|
||||
aria-labelledby="marvinJsModal"
|
||||
aria-hidden="true"
|
||||
data-backdrop="static"
|
||||
data-keyboard="false">
|
||||
<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">
|
||||
<%= text_field_tag :sketch_name %>
|
||||
</span>
|
||||
<p class="file-save-link"><span class="fas fa-save"></span> <%= t('SaveClose')%></p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="marvinjs-editor">
|
||||
<% if ENV['MARVINJS_API_KEY'] %>
|
||||
<div id="marvinjs-sketch" style="width: 600px; height: 480px"></div>
|
||||
<% elsif ENV['MARVINJS_URL'] %>
|
||||
<iframe id="marvinjs-sketch" src="<%= MarvinJsAsset.url %>" frameBorder="0"></iframe>
|
||||
<% end %>
|
||||
<div class="marvinjs-team-sketch-header"><%= t('marvinjs.team_drawings') %></div>
|
||||
<div class="marvinjs-team-sketch"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= javascript_include_tag("marvinjs_editor") %>
|
11
app/views/shared/_marvinjs_modal_sketch.html.erb
Normal file
11
app/views/shared/_marvinjs_modal_sketch.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="sketch-container" data-sketch-id="<%= sketch.id %>" >
|
||||
<img src class="sketch-image">
|
||||
<%= hidden_field_tag :description, sketch.description %>
|
||||
<div class="sketch-name"><%= truncate(sketch.name, length: Constants::FILENAME_TRUNCATION_LENGTH) %></div>
|
||||
<div class="sketch-object">
|
||||
<% if sketch.object.protocol.my_module %>
|
||||
<%= t('marvinjs.task') %>: <%= truncate(sketch.object.protocol.my_module.name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -24,6 +24,14 @@
|
|||
<%= t("protocols.steps.new.tab_tables") %>
|
||||
</a>
|
||||
</li>
|
||||
<% if MarvinJsAsset.enabled? %>
|
||||
<li role="presentation" id="new-step-sketch-tab">
|
||||
<a href="#new-step-sketch" data-toggle="tab" data-no-turbolink="true">
|
||||
<span class="fas fa-file-invoice"></span>
|
||||
<%= t('marvinjs.checmical_drawing') %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" role="tabpanel" id="new-step-main">
|
||||
|
@ -84,4 +92,15 @@
|
|||
<%= t("protocols.steps.new.add_table") %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if MarvinJsAsset.enabled? %>
|
||||
<div class="tab-pane" role="tabpanel" id="new-step-sketch">
|
||||
<div class="sketch-container">
|
||||
<%= f.nested_fields_for :marvin_js_assets do |ff| %>
|
||||
<% next unless ff.object.description %>
|
||||
<%= render partial: 'assets/marvinjs/marvin_sketch_card_placeholder.html.erb',
|
||||
locals: { sketch: ff.object, edit_page: true, ff: ff} %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
<div class="col-xs-12 attachments-actions">
|
||||
<div class="col-md-4 drag_n_drop_label">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="col-xl-8 col-md-12">
|
||||
<div class="attachemnts-header pull-right">
|
||||
<% if MarvinJsAsset.enabled? && (can_manage_protocol_in_module?(step.protocol) || can_manage_protocol_in_repository?(step.protocol)) %>
|
||||
<%= render partial: '/assets/marvinjs/create_marvin_sketch_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attacments#att-#{step.id}" } %>
|
||||
<% end %>
|
||||
<%= render partial: '/assets/wopi/create_wopi_file_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step' } %>
|
||||
<div class="dropdown attachments-order" id="dd-att-step-<%= step.id %>">
|
||||
|
@ -32,8 +36,13 @@
|
|||
|
||||
<div class="col-xs-12 attacments" id="att-<%= step.id %>">
|
||||
<% assets.each_with_index do |asset, i| %>
|
||||
<%= render partial: 'steps/attachments/item.html.erb',
|
||||
<% if asset.class.name == 'Asset' %>
|
||||
<%= render partial: 'steps/attachments/item.html.erb',
|
||||
locals: { asset: asset, i: i, assets_count: assets.count, step: step } %>
|
||||
<% elsif asset.class.name == 'MarvinJsAsset' %>
|
||||
<%= render partial: 'assets/marvinjs/marvin_sketch_card.html.erb',
|
||||
locals: { sketch: asset, i:i, assets_count: assets.count, step: step} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
|
@ -67,6 +67,7 @@ Rails.application.config.assets.precompile +=
|
|||
Rails.application.config.assets.precompile += %w(datatables.js)
|
||||
Rails.application.config.assets.precompile += %w(search/index.js)
|
||||
Rails.application.config.assets.precompile += %w(global_activities/side_pane.js)
|
||||
Rails.application.config.assets.precompile += %w(marvinjs_editor.js)
|
||||
Rails.application.config.assets.precompile += %w(navigation.js)
|
||||
Rails.application.config.assets.precompile += %w(secondary_navigation.js)
|
||||
Rails.application.config.assets.precompile += %w(datatables.css)
|
||||
|
|
|
@ -2144,3 +2144,10 @@ en:
|
|||
new: "https://support.scinote.net/hc/en-us/articles/360004627792"
|
||||
visibility: "https://support.scinote.net/hc/en-us/articles/360004627472"
|
||||
manage_columns: "https://support.scinote.net/hc/en-us/articles/360004695831"
|
||||
marvinjs:
|
||||
new_sketch: "New sketch"
|
||||
new_button: "New chemical drawing"
|
||||
checmical_drawing: "Chemical drawings"
|
||||
team_drawings: "Team drawings"
|
||||
task: "Task"
|
||||
no_sketches_found: "No sketches found"
|
|
@ -450,6 +450,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
# tinyMCE image uploader endpoint
|
||||
resources :tiny_mce_assets, only: [:update]
|
||||
post '/tinymce_assets', to: 'tiny_mce_assets#create', as: :tiny_mce_assets
|
||||
|
||||
resources :results, only: [:update, :destroy] do
|
||||
|
@ -676,6 +677,12 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :marvin_js_assets, only: %i(create update destroy show) do
|
||||
collection do
|
||||
get :team_sketches
|
||||
end
|
||||
end
|
||||
|
||||
post 'global_activities', to: 'global_activities#index'
|
||||
|
||||
constraints WopiSubdomain do
|
||||
|
|
15
db/migrate/20190426185413_create_marvin_js_assets.rb
Normal file
15
db/migrate/20190426185413_create_marvin_js_assets.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateMarvinJsAssets < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :marvin_js_assets do |t|
|
||||
t.bigint :team_id
|
||||
t.string :description
|
||||
t.references :object, polymorphic: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
change_column :marvin_js_assets, :id, :bigint
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNameToMarvinJsAssets < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_column :marvin_js_assets, :name, :string
|
||||
end
|
||||
end
|
13
db/schema.rb
13
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20190410110605) do
|
||||
ActiveRecord::Schema.define(version: 20190427115413) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -180,6 +180,17 @@ ActiveRecord::Schema.define(version: 20190410110605) do
|
|||
t.index ["restored_by_id"], name: "index_experiments_on_restored_by_id"
|
||||
end
|
||||
|
||||
create_table "marvin_js_assets", force: :cascade do |t|
|
||||
t.bigint "team_id"
|
||||
t.string "description"
|
||||
t.string "object_type"
|
||||
t.bigint "object_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "name"
|
||||
t.index ["object_type", "object_id"], name: "index_marvin_js_assets_on_object_type_and_object_id"
|
||||
end
|
||||
|
||||
create_table "my_module_groups", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
|
9
spec/factories/marvin_js_assets.rb
Normal file
9
spec/factories/marvin_js_assets.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :marvin_js_asset do
|
||||
team_id 1
|
||||
description 'MyString'
|
||||
object ''
|
||||
end
|
||||
end
|
120
vendor/assets/javascripts/marvinjslauncher.js
vendored
Normal file
120
vendor/assets/javascripts/marvinjslauncher.js
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
(function (win) {
|
||||
|
||||
function _getWrapperElement (id) {
|
||||
var re = new RegExp(/^#.*/);
|
||||
if (typeof id !== "string") {
|
||||
return null;
|
||||
}
|
||||
// remove hash mark if present
|
||||
return document.getElementById( (re.test(id)) ? id.substr(1) : id );
|
||||
}
|
||||
|
||||
function _getPackage (wrapperElement) {
|
||||
if (typeof wrapperElement.contentWindow.marvin != "undefined") {
|
||||
return wrapperElement.contentWindow.marvin;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function _createPackage(elementId, resolve, reject) {
|
||||
if(elementId == null){
|
||||
reject("Element id can not be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapperElement = _getWrapperElement(elementId);
|
||||
|
||||
if (wrapperElement == null) {
|
||||
reject("Unable to get element with id: " + elementId);
|
||||
return;
|
||||
}
|
||||
|
||||
var marvinPackage = _getPackage(wrapperElement);
|
||||
if (marvinPackage) {
|
||||
marvinPackage.onReady(function() {
|
||||
resolve(marvinPackage);
|
||||
});
|
||||
} else { // use listener
|
||||
wrapperElement.addEventListener("load", function handleSketchLoad (e) {
|
||||
var marvin = _getPackage(wrapperElement);
|
||||
if (marvin) {
|
||||
marvin.onReady(function() {
|
||||
resolve(marvin);
|
||||
});
|
||||
} else {
|
||||
reject("Unable to find marvin package");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _createEditor(elementId, resolve, reject) {
|
||||
if(elementId == null){
|
||||
reject("Element id can not be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapperElement = _getWrapperElement(elementId);
|
||||
|
||||
if (wrapperElement == null) {
|
||||
reject("Unable to get element with id: " + elementId);
|
||||
return;
|
||||
}
|
||||
|
||||
var marvinPackage = _getPackage(wrapperElement);
|
||||
if (marvinPackage) {
|
||||
marvinPackage.onReady(function() {
|
||||
if (typeof marvinPackage.sketcherInstance != "undefined") {
|
||||
resolve(_getPackage(wrapperElement).sketcherInstance);
|
||||
return;
|
||||
} else {
|
||||
reject("Unable to find sketcherInstance in element with id: " + elementId);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else { // use listener
|
||||
wrapperElement.addEventListener("load", function handleSketchLoad (e) {
|
||||
var marvin = _getPackage(wrapperElement);
|
||||
if (marvin) {
|
||||
marvin.onReady(function() {
|
||||
if (typeof marvin.sketcherInstance != 'undefined') {
|
||||
resolve(marvin.sketcherInstance);
|
||||
} else {
|
||||
reject("Unable to find sketcherInstance in iframe with id: " + elementId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject("Unable to find marvin package, cannot retrieve sketcher instance");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!("Promise" in win) && ("ES6Promise" in win) && ("polyfill" in win.ES6Promise)) {
|
||||
win.ES6Promise.polyfill();
|
||||
}
|
||||
|
||||
win.MarvinJSUtil = {
|
||||
"getEditor": function getEditor (elementId) {
|
||||
|
||||
function createEditor (resolve, reject) {
|
||||
_createEditor(elementId, resolve, reject);
|
||||
};
|
||||
|
||||
return new Promise(createEditor);
|
||||
}
|
||||
,"getPackage": function getPackage (elementId) {
|
||||
|
||||
function createPackage (resolve, reject) {
|
||||
_createPackage(elementId, resolve, reject);
|
||||
};
|
||||
|
||||
return new Promise(createPackage);
|
||||
}
|
||||
};;
|
||||
|
||||
|
||||
}(window));
|
||||
|
Loading…
Reference in a new issue