diff --git a/VERSION b/VERSION
index 6e66c4c20..bf4df28ef 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.28.1.3
+1.28.2
diff --git a/app/assets/images/icon_small/sequence-editor.svg b/app/assets/images/icon_small/sequence-editor.svg
new file mode 100644
index 000000000..b528c6c3f
--- /dev/null
+++ b/app/assets/images/icon_small/sequence-editor.svg
@@ -0,0 +1,10 @@
+
diff --git a/app/assets/javascripts/i18n_bundle.js b/app/assets/javascripts/i18n_bundle.js
new file mode 100644
index 000000000..04bbf3cf2
--- /dev/null
+++ b/app/assets/javascripts/i18n_bundle.js
@@ -0,0 +1,2 @@
+//= require i18n.js
+//= require i18n/translations
diff --git a/app/assets/javascripts/protocols/import_export/import.js b/app/assets/javascripts/protocols/import_export/import.js
index 000031aa4..d1e98c722 100644
--- a/app/assets/javascripts/protocols/import_export/import.js
+++ b/app/assets/javascripts/protocols/import_export/import.js
@@ -58,12 +58,41 @@ function importProtocolFromFile(
}
function getAssetBytes(folder, stepGuid, fileRef) {
- var stepPath = stepGuid ? stepGuid + '/' : '';
- var filePath = folder + stepPath + fileRef;
- var assetBytes = zipFiles.files[cleanFilePath(filePath)].asBinary();
+ const stepPath = stepGuid ? stepGuid + '/' : '';
+ const filePath = folder + stepPath + fileRef;
+ const assetBytes = zipFiles.files[cleanFilePath(filePath)].asBinary();
return window.btoa(assetBytes);
}
+ function getAssetPreview(folder, stepGuid, fileRef, fileName, fileType) {
+ if ($.inArray(fileType, ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']) > 0) {
+ return {
+ fileName: fileName,
+ fileType: fileType,
+ bytes: getAssetBytes(folder, stepGuid, fileRef)
+ };
+ } else {
+ const stepPath = stepGuid ? folder + stepGuid + '/' : folder;
+ let baseName;
+ baseName = fileRef.split('.');
+ baseName.pop();
+ baseName.join('.');
+ let previewFileRef = zipFiles.file(new RegExp(stepPath + 'previews/' + baseName));
+ if (previewFileRef.length > 0) {
+ const previewFileExt = previewFileRef[0].name.split('.').at(-1);
+ let previewFileName = fileName.split('.');
+ previewFileName.splice(-1, 1, previewFileExt);
+ previewFileName.join('.');
+ return {
+ fileName: previewFileName,
+ fileType: `image/${previewFileExt}`,
+ bytes: window.btoa(previewFileRef[0].asBinary())
+ };
+ }
+ }
+ return null;
+ }
+
/* Template functions */
function newPreviewElement(name, values) {
@@ -82,14 +111,14 @@ function importProtocolFromFile(
}
function newAssetElement(folder, stepGuid, fileRef, fileName, fileType) {
- var html = '
';
- var assetBytes;
- if ($.inArray(fileType, ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']) > 0) {
- assetBytes = getAssetBytes(folder, stepGuid, fileRef);
+ let html = '';
+ let assetPreview = getAssetPreview(folder, stepGuid, fileRef, fileName, fileType);
- html += '';
+ if (assetPreview) {
+ html += '';
html += '
';
}
+
html += '' + fileName + '';
html += '';
return $.parseHTML(html);
@@ -708,6 +737,7 @@ function importProtocolFromFile(
var assetId = $(this).attr('id');
var fileRef = $(this).attr('fileRef');
var fileName = $(this).children('fileName').text();
+
stepAssetJson.id = assetId;
stepAssetJson.fileName = fileName;
stepAssetJson.fileType = $(this).children('fileType').text();
@@ -723,6 +753,14 @@ function importProtocolFromFile(
fileRef
);
+ stepAssetJson.preview_image = getAssetPreview(
+ protocolFolders[index],
+ stepGuid,
+ fileRef,
+ fileName,
+ null
+ );
+
stepAssetsJson.push(stepAssetJson);
});
stepJson.assets = stepAssetsJson;
diff --git a/app/assets/javascripts/sitewide/active_storage_previews.js b/app/assets/javascripts/sitewide/active_storage_previews.js
index 32697ae23..7596bf168 100644
--- a/app/assets/javascripts/sitewide/active_storage_previews.js
+++ b/app/assets/javascripts/sitewide/active_storage_previews.js
@@ -17,6 +17,8 @@ var ActiveStoragePreviews = (function() {
if (img.retryCount >= RETRY_COUNT) return;
+ $(img).css('opacity', 0);
+
if (!$(img).parent().hasClass('processing')) $(img).parent().addClass('processing');
setTimeout(() => {
diff --git a/app/assets/javascripts/sitewide/iframe_modal.js b/app/assets/javascripts/sitewide/iframe_modal.js
new file mode 100644
index 000000000..70631dcb4
--- /dev/null
+++ b/app/assets/javascripts/sitewide/iframe_modal.js
@@ -0,0 +1,35 @@
+/* global iFrameModal */
+// General-purpose iframe modal. For closing the modal, you need will to take care of triggering
+// the 'hide' event on the modal itself, example from inside the iframe:
+// parent.document.getElementById('iFrameModal').dispatchEvent(new Event('hide'));
+
+$(document).on('turbolinks:load', function() {
+ window.iFrameModal = document.getElementById('iFrameModal');
+ let iFrameModalFrame = document.getElementById('iFrameModalFrame');
+
+ // Block from running when accessing page without defined iframe modal
+ // (sign in, reset password, accept invitation, 2fa)
+ if (!iFrameModalFrame || !iFrameModal) return;
+
+ window.showIFrameModal = (url) => {
+ iFrameModalFrame.setAttribute('src', url);
+ iFrameModal.classList.remove('hidden');
+ iFrameModal.dispatchEvent(new Event('shown'));
+ };
+
+ iFrameModal.addEventListener('hide', () => {
+ iFrameModal.classList.add('hidden');
+ iFrameModalFrame.removeAttribute('src');
+ iFrameModal.dispatchEvent(new Event('hidden'));
+ });
+
+ iFrameModal.addEventListener('shown', () => {
+ document.body.classList.add('overflow-hidden');
+ document.body.classList.remove('overflow-auto');
+ });
+
+ iFrameModal.addEventListener('hidden', () => {
+ document.body.classList.remove('overflow-hidden');
+ document.body.classList.add('overflow-auto');
+ });
+});
diff --git a/app/assets/javascripts/sitewide/marvinjs_editor.js b/app/assets/javascripts/sitewide/marvinjs_editor.js
index 60fa514d4..38021f77f 100644
--- a/app/assets/javascripts/sitewide/marvinjs_editor.js
+++ b/app/assets/javascripts/sitewide/marvinjs_editor.js
@@ -303,6 +303,12 @@ $(document).on('click', '.marvinjs-edit-button', function() {
});
});
+$(document).on('click', '.gene-sequence-edit-button', function() {
+ var editButton = $(this);
+ $('#filePreviewModal').modal('hide');
+ window.showIFrameModal(editButton.data('sequence-edit-url'));
+});
+
$(document).on('turbolinks:load', function() {
MarvinJsEditor = MarvinJsEditorApi();
if (MarvinJsEditor.enabled()) {
diff --git a/app/assets/stylesheets/reports.scss b/app/assets/stylesheets/reports.scss
index bf1d9604b..3c4c7d1eb 100644
--- a/app/assets/stylesheets/reports.scss
+++ b/app/assets/stylesheets/reports.scss
@@ -67,6 +67,8 @@ label {
* Global fix for handsontable
*/
.hot-table-container {
+ display: flex;
+ overflow: auto;
.ht_master .wtHolder {
height: auto !important;
width: auto !important;
diff --git a/app/assets/stylesheets/reports_print.scss b/app/assets/stylesheets/reports_print.scss
index 18ac71a92..46f7f3fbb 100644
--- a/app/assets/stylesheets/reports_print.scss
+++ b/app/assets/stylesheets/reports_print.scss
@@ -29,6 +29,8 @@ div.print-report {
}
.hot-table-container {
+ display: flex;
+ overflow: auto;
.ht_master .wtHolder {
overflow: hidden !important;
diff --git a/app/assets/stylesheets/shared/assets.scss b/app/assets/stylesheets/shared/assets.scss
index 9b212b738..af600921a 100644
--- a/app/assets/stylesheets/shared/assets.scss
+++ b/app/assets/stylesheets/shared/assets.scss
@@ -445,3 +445,15 @@
padding: 4px 8px;
white-space: nowrap;
}
+
+.sn-file-ove {
+ height: 1.5rem;
+ width: 1.5rem;
+
+ &::before {
+ content: url("icon_small/sequence-editor.svg");
+ display: inline-block;
+ margin: auto;
+ width: 100%;
+ }
+}
diff --git a/app/assets/stylesheets/shared/file_preview.scss b/app/assets/stylesheets/shared/file_preview.scss
index 145147c91..dbf903698 100644
--- a/app/assets/stylesheets/shared/file_preview.scss
+++ b/app/assets/stylesheets/shared/file_preview.scss
@@ -26,11 +26,22 @@
.file-preview-container {
align-items: center;
+ background-color: var(--sn-color-white);
display: flex;
- height: 100%;
+ height: calc(100% - 4rem);
justify-content: center;
+ margin: 2rem;
text-align: center;
- width: 100%;
+ width: calc(100% - 4rem);
+
+ .asset-image {
+ background-color: var(--sn-white);
+ }
+
+ .gene-sequence-asset {
+ height: 500px;
+ width: 500px;
+ }
&.processing {
background-image: url("/images/medium/loading_white.svg");
diff --git a/app/assets/stylesheets/shared_styles/elements/input_fields.scss b/app/assets/stylesheets/shared_styles/elements/input_fields.scss
index bdb53169e..1a1035a1b 100644
--- a/app/assets/stylesheets/shared_styles/elements/input_fields.scss
+++ b/app/assets/stylesheets/shared_styles/elements/input_fields.scss
@@ -15,7 +15,7 @@
.sci-input-field {
@include font-button;
animation-timing-function: $timing-function-sharp;
- border: $border-secondary;
+ border: 1px solid var(--sn-light-grey);
border-radius: $border-radius-default !important;
box-shadow: none;
height: 36px;
@@ -24,12 +24,18 @@
transition: .3s;
width: 100%;
+ &:hover {
+ border: 1px solid var(--sn-science-blue-hover);
+ }
+
&:focus {
border: $border-focus;
}
&:disabled {
- background: transparent;
+ background-color: var(--sn-super-light-grey);
+ color: var(--sn-light-grey);
+ border: 1px solid var(--sn-light-grey);
}
&::placeholder {
diff --git a/app/assets/stylesheets/sn_icon_font.css b/app/assets/stylesheets/sn_icon_font.css
new file mode 100644
index 000000000..3c023c6f9
--- /dev/null
+++ b/app/assets/stylesheets/sn_icon_font.css
@@ -0,0 +1,4 @@
+/*
+*= require sn-icon-font
+*= require sn-inter-font
+*/
diff --git a/app/assets/stylesheets/tailwind/inputs.css b/app/assets/stylesheets/tailwind/inputs.css
index f64561fa8..5018e7216 100644
--- a/app/assets/stylesheets/tailwind/inputs.css
+++ b/app/assets/stylesheets/tailwind/inputs.css
@@ -29,6 +29,16 @@
box-shadow: none;
}
+ .sci-input-container-v2 input:hover {
+ border-color: var(--sn-science-blue-hover);
+ }
+
+ .sci-input-container-v2 input:disabled {
+ background-color: var(--sn-super-light-grey);
+ color: var(--sn-light-grey);
+ border: 1px solid var(--sn-light-grey);
+ }
+
.sci-input-container-v2 .sn-icon {
@apply m-2;
color: var(--sn-black)
diff --git a/app/controllers/gene_sequence_assets_controller.rb b/app/controllers/gene_sequence_assets_controller.rb
new file mode 100644
index 000000000..4d438317f
--- /dev/null
+++ b/app/controllers/gene_sequence_assets_controller.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+class GeneSequenceAssetsController < ApplicationController
+ include ActiveStorage::SetCurrent
+
+ skip_before_action :verify_authenticity_token
+
+ before_action :check_open_vector_service_enabled, except: %i(new edit)
+ before_action :load_vars, except: %i(new create)
+ before_action :load_create_vars, only: %i(new create)
+
+ before_action :check_read_permission
+ before_action :check_manage_permission, only: %i(new update create)
+
+ def new
+ render :edit, layout: false
+ end
+
+ def edit
+ @file_url = rails_representation_url(@asset.file)
+ @file_name = @asset.render_file_name
+ log_activity('sequence_asset_edit_started')
+ render :edit, layout: false
+ end
+
+ def create
+ save_asset!
+ log_activity('sequence_asset_added')
+ head :ok
+ end
+
+ def update
+ save_asset!
+ log_activity('sequence_asset_edit_finished')
+ head :ok
+ end
+
+ def destroy
+ log_activity('sequence_asset_deleted')
+ head :ok
+ end
+
+ private
+
+ def save_asset!
+ ActiveRecord::Base.transaction do
+ ensure_asset!
+
+ @asset.file.purge
+ @asset.preview_image.purge
+
+ @asset.file.attach(
+ io: StringIO.new(params[:sequence_data].to_json),
+ filename: "#{params[:sequence_name]}.json"
+ )
+
+ @asset.preview_image.attach(
+ io: StringIO.new(Base64.decode64(params[:base64_image].split(',').last)),
+ filename: "#{params[:sequence_name]}.png"
+ )
+
+ file = @asset.file
+
+ file.blob.metadata['asset_type'] = 'gene_sequence'
+ file.blob.metadata['name'] = params[:sequence_name]
+ file.save!
+ @asset.view_mode ||= @parent.assets_view_mode
+ @asset.save!
+ end
+ end
+
+ def ensure_asset!
+ return if @asset
+ return unless @parent
+
+ @asset = @parent.assets.create!(last_modified_by: current_user, team: current_team)
+ end
+
+ def load_vars
+ @ove_enabled = OpenVectorEditorService.enabled?
+ @asset = current_team.assets.find_by(id: params[:id])
+ return render_404 unless @asset
+
+ @parent ||= @asset.step
+ @parent ||= @asset.result
+
+ case @parent
+ when Step
+ @protocol = @parent.protocol
+ when Result
+ @my_module = @parent.my_module
+ end
+ end
+
+ def load_create_vars
+ @ove_enabled = OpenVectorEditorService.enabled?
+ @parent = case params[:parent_type]
+ when 'Step'
+ Step.find_by(id: params[:parent_id])
+ when 'Result'
+ Result.find_by(id: params[:parent_id])
+ end
+
+ case @parent
+ when Step
+ @protocol = @parent.protocol
+ when Result
+ @result = @parent
+ end
+ end
+
+ def check_read_permission
+ case @parent
+ when Step
+ return render_403 unless can_read_protocol_in_module?(@protocol) ||
+ can_read_protocol_in_repository?(@protocol)
+ when Result
+ return render_403 unless can_read_my_module?(@my_module)
+ else
+ render_403
+ end
+ end
+
+ def check_manage_permission
+ render_403 unless asset_managable?
+ end
+
+ def check_open_vector_service_enabled
+ render_403 unless OpenVectorEditorService.enabled?
+ end
+
+ helper_method :asset_managable?
+ def asset_managable?
+ case @parent
+ when Step
+ can_manage_step?(@parent)
+ when Result
+ can_manage_my_module?(@parent)
+ else
+ false
+ end
+ end
+
+ def log_activity(type_of, project = nil, message_items = {})
+ return unless @parent.is_a?(Step)
+
+ my_module = @parent.my_module
+ default_items = {
+ protocol: @parent.protocol.id,
+ step: @parent.id,
+ asset_name: { id: @asset.id, value_for: 'file_name' },
+ step_position: { id: @parent.id, value_for: 'position_plus_one' }
+ }
+
+ if my_module
+ project = my_module.project
+ default_items[:my_module] = my_module.id
+ type_of = "task_#{type_of}".to_sym
+ else
+ type_of = "protocol_#{type_of}".to_sym
+ end
+
+ message_items = default_items.merge(message_items)
+
+ Activities::CreateActivityService.call(
+ activity_type: type_of,
+ owner: current_user,
+ team: @parent.protocol.team,
+ subject: @parent.protocol,
+ message_items: message_items,
+ project: project
+ )
+ end
+end
diff --git a/app/controllers/global_activities_controller.rb b/app/controllers/global_activities_controller.rb
index 6e29f7375..4d05101c1 100644
--- a/app/controllers/global_activities_controller.rb
+++ b/app/controllers/global_activities_controller.rb
@@ -65,13 +65,17 @@ class GlobalActivitiesController < ApplicationController
end
def team_filter
- render json: current_user.teams.ordered.global_activity_filter(activity_filters, params[:query])
+ teams = current_user.teams.ordered.global_activity_filter(activity_filters, params[:query])
+ render json: teams.select(:id, :name)
+ .map { |i| { value: i[:id], label: escape_input(i[:name]) } }
end
def user_filter
filter = activity_filters
filter = { subjects: { MyModule: [params[:my_module_id].to_i] } } if params[:my_module_id]
- render json: current_user.global_activity_filter(filter, params[:query])
+ users = current_user.global_activity_filter(filter, params[:query])
+ render json: users.select(:full_name, :id)
+ .map { |i| { label: escape_input(i[:full_name]), value: i[:id] } }
end
def project_filter
diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb
index 2019bce38..ce161bed7 100644
--- a/app/controllers/protocols_controller.rb
+++ b/app/controllers/protocols_controller.rb
@@ -697,6 +697,12 @@ class ProtocolsController < ApplicationController
asset_file_name = asset_guid.to_s + File.extname(asset.file_name).to_s
ostream.put_next_entry("#{step_dir}/#{asset_file_name}")
ostream.print(asset.file.download)
+
+ next unless asset.preview_image.attached?
+
+ asset_preview_image_name = asset_guid.to_s + File.extname(asset.preview_image_file_name).to_s
+ ostream.put_next_entry("#{step_dir}/previews/#{asset_preview_image_name}")
+ ostream.print(asset.preview_image.download)
end
end
ostream = step.tiny_mce_assets.save_to_eln(ostream, step_dir)
diff --git a/app/helpers/file_icons_helper.rb b/app/helpers/file_icons_helper.rb
index 9b03edbfb..cab2503b5 100644
--- a/app/helpers/file_icons_helper.rb
+++ b/app/helpers/file_icons_helper.rb
@@ -40,6 +40,8 @@ module FileIconsHelper
image_link = 'icon_small/pptx_file.svg'
elsif asset.file.attached? && asset.file.metadata['asset_type'] == 'marvinjs'
image_link = 'icon_small/marvinjs_file.svg'
+ elsif asset.file.attached? && asset.file.metadata['asset_type'] == 'gene_sequence'
+ image_link = 'icon_small/sequence-editor.svg'
end
# Now check for custom mappings or possible overrides
diff --git a/app/javascript/packs/open_vector_editor.js b/app/javascript/packs/open_vector_editor.js
new file mode 100644
index 000000000..af3675194
--- /dev/null
+++ b/app/javascript/packs/open_vector_editor.js
@@ -0,0 +1,2 @@
+import '@teselagen/ove';
+import '@teselagen/ove/style.css';
diff --git a/app/javascript/packs/vue/open_vector_editor.js b/app/javascript/packs/vue/open_vector_editor.js
new file mode 100644
index 000000000..c8ca74ac9
--- /dev/null
+++ b/app/javascript/packs/vue/open_vector_editor.js
@@ -0,0 +1,11 @@
+import TurbolinksAdapter from 'vue-turbolinks';
+import Vue from 'vue/dist/vue.esm';
+import OpenVectorEditor from '../../vue/ove/OpenVectorEditor.vue';
+
+Vue.use(TurbolinksAdapter);
+Vue.prototype.i18n = window.I18n;
+
+new Vue({
+ el: '#open-vector-editor',
+ components: { OpenVectorEditor }
+});
diff --git a/app/javascript/vue/ove/OpenVectorEditor.vue b/app/javascript/vue/ove/OpenVectorEditor.vue
new file mode 100644
index 000000000..066b6f58f
--- /dev/null
+++ b/app/javascript/vue/ove/OpenVectorEditor.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
diff --git a/app/javascript/vue/protocol/attachments.vue b/app/javascript/vue/protocol/attachments.vue
index ca24c8ac1..9ac6afba3 100644
--- a/app/javascript/vue/protocol/attachments.vue
+++ b/app/javascript/vue/protocol/attachments.vue
@@ -1,5 +1,5 @@
-
+
-
+
{{ i18n.t('protocols.steps.timestamp', {date: step.attributes.created_at, user: step.attributes.created_by}) }}
-
-
-
+
@@ -308,7 +307,6 @@
$.get(this.urls.attachments_url, (result) => {
this.attachments = result.data
-
if (this.attachments.findIndex((e) => e.attributes.attached === false) >= 0) {
setTimeout(() => {
this.loadAttachments()
diff --git a/app/javascript/vue/protocol/step_attachments/context_menu.vue b/app/javascript/vue/protocol/step_attachments/context_menu.vue
index a1456fb9c..f55c4438e 100644
--- a/app/javascript/vue/protocol/step_attachments/context_menu.vue
+++ b/app/javascript/vue/protocol/step_attachments/context_menu.vue
@@ -20,6 +20,12 @@
{{ attachment.attributes.wopi_context.button_text }}
+
+
+
+ {{ i18n.t('open_vector_editor.edit_sequence') }}
+
+
-
+
{{ i18n.t('assets.file_preview.edit_in_marvinjs') }}
@@ -129,6 +135,9 @@
deleteAttachment() {
this.deleteModal = false
this.$emit('attachment:delete')
+ },
+ openOVEditor(url) {
+ window.showIFrameModal(url);
}
}
}
diff --git a/app/javascript/vue/protocol/step_attachments/file_modal.vue b/app/javascript/vue/protocol/step_attachments/file_modal.vue
index edf577a30..e5a3f0613 100644
--- a/app/javascript/vue/protocol/step_attachments/file_modal.vue
+++ b/app/javascript/vue/protocol/step_attachments/file_modal.vue
@@ -34,7 +34,7 @@
{{ i18n.t("protocols.steps.attachments.file_modal.drag_zone_notification", {position: step.attributes.position + 1}) }}
-
+
{{ i18n.t("protocols.steps.attachments.file_modal.or") }}
@@ -44,6 +44,12 @@
{{ i18n.t('assets.create_wopi_file.button_text') }}
+
@@ -148,6 +154,10 @@
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
}
});
+ },
+ openOVEditor() {
+ $(this.$refs.modal).modal('hide');
+ window.showIFrameModal(this.step.attributes.open_vector_editor_context.new_sequence_asset_url);
}
}
}
diff --git a/app/javascript/vue/protocol/step_attachments/list.vue b/app/javascript/vue/protocol/step_attachments/list.vue
index 1d41f10d3..e686cbca5 100644
--- a/app/javascript/vue/protocol/step_attachments/list.vue
+++ b/app/javascript/vue/protocol/step_attachments/list.vue
@@ -2,6 +2,7 @@
<% end %>
diff --git a/app/views/my_modules/modals/_new_modal.html.erb b/app/views/my_modules/modals/_new_modal.html.erb
index 967fbca56..22666ddd5 100644
--- a/app/views/my_modules/modals/_new_modal.html.erb
+++ b/app/views/my_modules/modals/_new_modal.html.erb
@@ -80,7 +80,7 @@
- <%= f.button t('experiments.canvas.new_my_module_modal.create'), class: "btn btn-primary" %>
+ <%= f.submit t('experiments.canvas.new_my_module_modal.create'), class: "btn btn-primary" %>
diff --git a/app/views/projects/index/modals/_new_project.html.erb b/app/views/projects/index/modals/_new_project.html.erb
index cffceffad..9b8d10579 100644
--- a/app/views/projects/index/modals/_new_project.html.erb
+++ b/app/views/projects/index/modals/_new_project.html.erb
@@ -40,7 +40,7 @@
diff --git a/app/views/protocols/index/_new_protocol_modal.html.erb b/app/views/protocols/index/_new_protocol_modal.html.erb
index e8153f382..ad0897414 100644
--- a/app/views/protocols/index/_new_protocol_modal.html.erb
+++ b/app/views/protocols/index/_new_protocol_modal.html.erb
@@ -41,7 +41,7 @@
diff --git a/app/views/result_assets/_edit.html.erb b/app/views/result_assets/_edit.html.erb
index 2c0d76d44..5328a80c0 100644
--- a/app/views/result_assets/_edit.html.erb
+++ b/app/views/result_assets/_edit.html.erb
@@ -17,8 +17,7 @@
- <%= f.button t("general.save"),
- class: 'btn btn-primary save-result' %>
+ <%= f.submit t("general.save"), class: 'btn btn-primary save-result' %>
<% end %>
diff --git a/app/views/result_tables/_edit.html.erb b/app/views/result_tables/_edit.html.erb
index 5c93ceac9..b2615ad88 100644
--- a/app/views/result_tables/_edit.html.erb
+++ b/app/views/result_tables/_edit.html.erb
@@ -18,8 +18,7 @@
- <%= f.button t("general.save"),
- class: 'btn btn-primary save-result' %>
+ <%= f.submit t("general.save"), class: 'btn btn-primary save-result' %>
<% end %>
diff --git a/app/views/result_tables/_new.html.erb b/app/views/result_tables/_new.html.erb
index 47a5ca52d..890dc734b 100644
--- a/app/views/result_tables/_new.html.erb
+++ b/app/views/result_tables/_new.html.erb
@@ -17,8 +17,7 @@
- <%= f.button t("result_tables.new.create"),
- class: 'btn btn-primary save-result' %>
+ <%= f.submit t("result_tables.new.create"), class: 'btn btn-primary save-result' %>
<% end %>
diff --git a/app/views/result_texts/_edit.html.erb b/app/views/result_texts/_edit.html.erb
index 1001e20ad..99f4efaa7 100644
--- a/app/views/result_texts/_edit.html.erb
+++ b/app/views/result_texts/_edit.html.erb
@@ -19,8 +19,7 @@
- <%= f.button t("general.save"),
- class: 'btn btn-primary save-result' %>
+ <%= f.submit t("general.save"), class: 'btn btn-primary save-result' %>
<% end %>
diff --git a/app/views/result_texts/_new.html.erb b/app/views/result_texts/_new.html.erb
index d80694d2a..fb2b42be4 100644
--- a/app/views/result_texts/_new.html.erb
+++ b/app/views/result_texts/_new.html.erb
@@ -18,8 +18,7 @@
- <%= f.button t("result_texts.new.create"),
- class: 'btn btn-primary save-result' %>
+ <%= f.submit t("result_texts.new.create"), class: 'btn btn-primary save-result' %>
<% end %>
diff --git a/app/views/search/results/_assets.html.erb b/app/views/search/results/_assets.html.erb
index 997e3ee81..6da353cd6 100644
--- a/app/views/search/results/_assets.html.erb
+++ b/app/views/search/results/_assets.html.erb
@@ -1,7 +1,9 @@
<% @asset_results.each do |asset| %>
-
+
<% if asset.blob.metadata["asset_type"] == 'marvinjs' %>
+ <% elsif asset.blob.metadata["asset_type"] == 'gene_sequence' %>
+
<% else %>
<% if wopi_file?(asset) %>
<%= file_extension_icon(asset) %>
diff --git a/app/views/shareable_links/my_module_results_show.html.erb b/app/views/shareable_links/my_module_results_show.html.erb
index 31be4be4c..604804799 100644
--- a/app/views/shareable_links/my_module_results_show.html.erb
+++ b/app/views/shareable_links/my_module_results_show.html.erb
@@ -6,7 +6,7 @@
-
+
<%= render partial: 'shareable_links/my_modules/header_actions' %>
diff --git a/app/views/shareable_links/my_modules/results/_comments_list.html.erb b/app/views/shareable_links/my_modules/results/_comments_list.html.erb
index 2143a1f94..dbd59eb23 100644
--- a/app/views/shareable_links/my_modules/results/_comments_list.html.erb
+++ b/app/views/shareable_links/my_modules/results/_comments_list.html.erb
@@ -2,13 +2,13 @@