mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Merge branch 'features/august-release' into SCI-8807-notifications
This commit is contained in:
commit
242df0632d
2
Gemfile
2
Gemfile
|
@ -73,7 +73,7 @@ gem 'nested_form_fields'
|
|||
gem 'nokogiri', '~> 1.14.3' # HTML/XML parser
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
gem 'rgl' # Graph framework for project diagram calculations
|
||||
gem 'roo', '~> 2.8.2' # Spreadsheet parser
|
||||
gem 'roo', '~> 2.10.0' # Spreadsheet parser
|
||||
gem 'rotp'
|
||||
gem 'rqrcode', '~> 2.0' # QR code generator
|
||||
gem 'rubyzip', '>= 2.3.0' # will load new rubyzip version
|
||||
|
|
125
Gemfile.lock
125
Gemfile.lock
|
@ -60,47 +60,47 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actioncable (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionmailbox (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionmailer (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionpack (7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actiontext (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionview (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -110,24 +110,24 @@ GEM
|
|||
activemodel (>= 4.1, < 7.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activejob (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activerecord (7.0.5)
|
||||
activemodel (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activemodel (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activerecord (7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activestorage (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activestorage (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.5)
|
||||
activesupport (7.0.5.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -412,8 +412,8 @@ GEM
|
|||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.2)
|
||||
minitest (5.18.0)
|
||||
mini_portile2 (2.8.4)
|
||||
minitest (5.18.1)
|
||||
momentjs-rails (2.17.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.7.1)
|
||||
|
@ -505,26 +505,27 @@ GEM
|
|||
rack
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.5)
|
||||
actioncable (= 7.0.5)
|
||||
actionmailbox (= 7.0.5)
|
||||
actionmailer (= 7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
actiontext (= 7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activemodel (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
rails (7.0.5.1)
|
||||
actioncable (= 7.0.5.1)
|
||||
actionmailbox (= 7.0.5.1)
|
||||
actionmailer (= 7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actiontext (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.5)
|
||||
railties (= 7.0.5.1)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
rails-dom-testing (2.1.1)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
|
@ -538,9 +539,9 @@ GEM
|
|||
railties (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
railties (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
@ -561,7 +562,7 @@ GEM
|
|||
pairing_heap (>= 0.3.0)
|
||||
rexml (~> 3.2, >= 3.2.4)
|
||||
stream (~> 0.5.3)
|
||||
roo (2.8.3)
|
||||
roo (2.10.0)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 1.3.0, < 3.0.0)
|
||||
rotp (6.2.2)
|
||||
|
@ -664,7 +665,7 @@ GEM
|
|||
thor (1.2.2)
|
||||
tilt (2.2.0)
|
||||
timecop (0.9.6)
|
||||
timeout (0.3.2)
|
||||
timeout (0.4.0)
|
||||
turbolinks (5.1.1)
|
||||
turbolinks-source (~> 5.1)
|
||||
turbolinks-source (5.2.0)
|
||||
|
@ -772,7 +773,7 @@ DEPENDENCIES
|
|||
omniauth-okta!
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
overcommit
|
||||
pg (~> 1.1)
|
||||
pg (~> 1.5)
|
||||
pg_search
|
||||
pry
|
||||
pry-byebug
|
||||
|
@ -781,13 +782,13 @@ DEPENDENCIES
|
|||
puma
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (~> 7.0.5)
|
||||
rails (~> 7.0.5.1)
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
rails_autolink (~> 1.1, >= 1.1.6)
|
||||
recaptcha
|
||||
rgl
|
||||
roo (~> 2.8.2)
|
||||
roo (~> 2.10.0)
|
||||
rotp
|
||||
rqrcode (~> 2.0)
|
||||
rspec-rails
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/* global ActiveStoragePreviews */
|
||||
|
||||
(function() {
|
||||
$('.attachment-preview, .asset-inline-image')
|
||||
.on('load', (event) => ActiveStoragePreviews.showPreview(event))
|
||||
.on('error', (event) => ActiveStoragePreviews.reCheckPreview(event));
|
||||
}());
|
|
@ -1,7 +0,0 @@
|
|||
/* global ActiveStoragePreviews */
|
||||
|
||||
(function() {
|
||||
$('.attachment-preview .asset-thumbnail-image')
|
||||
.on('load', (event) => ActiveStoragePreviews.showPreview(event))
|
||||
.on('error', (event) => ActiveStoragePreviews.reCheckPreview(event));
|
||||
}());
|
|
@ -37,6 +37,7 @@ var ProjectsIndex = (function() {
|
|||
// Arrays with selected project and folder IDs shared between both views
|
||||
var selectedProjects = [];
|
||||
var selectedProjectFolders = [];
|
||||
var singleSelectedProject;
|
||||
var destinationFolder;
|
||||
|
||||
// Init new project folder modal function
|
||||
|
@ -163,6 +164,7 @@ var ProjectsIndex = (function() {
|
|||
ev.preventDefault();
|
||||
|
||||
const projectId = $(this).data('projectId');
|
||||
singleSelectedProject = projectId;
|
||||
|
||||
// Load HTML to refresh users list
|
||||
$.ajax({
|
||||
|
@ -223,7 +225,7 @@ var ProjectsIndex = (function() {
|
|||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
project_ids: selectedProjects,
|
||||
project_ids: selectedProjects.length > 0 ? selectedProjects : [singleSelectedProject],
|
||||
project_folder_ids: selectedProjectFolders
|
||||
},
|
||||
success: function(data) {
|
||||
|
@ -232,7 +234,7 @@ var ProjectsIndex = (function() {
|
|||
HelperModule.flashAlertMsg(data.flash, 'success');
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
HelperModule.flashAlertMsg(I18n.t('projects.export_projects.error_flash'), 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
.on('shown.bs.modal', function() {
|
||||
$(`${protocolModal} #protocol_name`).parent().removeClass('error');
|
||||
$(`${protocolModal} #protocol_name`).val('');
|
||||
const protocolName = $(`a[data-target="${protocolModal}"]`).attr('data-protocol-name');
|
||||
if (protocolName) {
|
||||
$(this).find('.sci-input-field').val(protocolName);
|
||||
}
|
||||
$(this).find('.sci-input-field').focus();
|
||||
})
|
||||
.on('ajax:error', 'form', function(e, error) {
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
el.handsontable('getInstance').getPlugin('columnSorting').sort(3, order);
|
||||
} else {
|
||||
metadata = JSON.parse(metadataJson.val() || '{}');
|
||||
metadata = metadataJson.val() || {};
|
||||
el.handsontable({
|
||||
disableVisualSelection: true,
|
||||
rowHeaders: tableRowHeaders(metadata.plateTemplate),
|
||||
|
|
|
@ -162,20 +162,9 @@
|
|||
}).on('shown.bs.modal', '#export-repositories-modal', function() {
|
||||
if (!CHECKBOX_SELECTOR) return;
|
||||
|
||||
const selectedInventoriesCount = CHECKBOX_SELECTOR.selectedRows.length;
|
||||
const firstDescription = $(this).find('.description-p1');
|
||||
const teamName = firstDescription.data('team-name');
|
||||
const exportButton = $(this).find('#export-repositories-modal-submit');
|
||||
const exportURL = exportButton.data('export-url');
|
||||
|
||||
firstDescription.html(I18n.t(
|
||||
'repositories.index.modal_export.description_p1_html',
|
||||
{
|
||||
team_name: teamName,
|
||||
count: selectedInventoriesCount
|
||||
}
|
||||
));
|
||||
|
||||
exportButton.on('click', function() {
|
||||
$.ajax({
|
||||
url: exportURL,
|
||||
|
|
|
@ -48,7 +48,8 @@ var ChecklistColumnHelper = (function() {
|
|||
optionClass: 'checkbox-icon',
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
$hiddenField.val(JSON.stringify(dropdownSelector.getValues('#' + select)));
|
||||
let currentValues = dropdownSelector.getValues('#' + select);
|
||||
$hiddenField.val(currentValues.length ? JSON.stringify(currentValues) : null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -532,7 +532,7 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
TABLE.ColSizes = state.ColSizes;
|
||||
|
||||
restoreColumnSizes();
|
||||
setTimeout(restoreColumnSizes, 100);
|
||||
}
|
||||
|
||||
function dataTableInit() {
|
||||
|
@ -576,6 +576,15 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
// force width of checkbox column
|
||||
data[0] = 30;
|
||||
|
||||
// perserve widths of invisible columns or enforce min width
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] === 0) {
|
||||
let minWidth = parseInt($(TABLE.column(i).header()).css('min-width'), 10);
|
||||
data[i] = colSizeMap[i] || minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
state.ColSizes = data;
|
||||
|
||||
$(TABLE_WRAPPER_ID).find('.table').addClass('table--resizable-columns');
|
||||
|
@ -773,8 +782,8 @@ var RepositoryDatatable = (function(global) {
|
|||
}
|
||||
});
|
||||
},
|
||||
stateSaveCallback: function(settings, data) {
|
||||
if (Object.keys(colSizeMap).length === 0) return;
|
||||
stateSaveCallback: function(_, data) {
|
||||
if (Object.keys(colSizeMap).length === 0) return true;
|
||||
|
||||
let colSizes = [];
|
||||
|
||||
|
|
|
@ -237,24 +237,22 @@ var RepositoryColumns = (function() {
|
|||
}
|
||||
|
||||
function toggleColumnVisibility() {
|
||||
var lis = $(columnsList).find('.vis');
|
||||
lis.on('click', function(event) {
|
||||
var self = $(this);
|
||||
var li = self.closest('li');
|
||||
var column = TABLE.column(li.attr('data-position'));
|
||||
$(columnsList).find('.vis').on('click', function(event) {
|
||||
const $this = $(this);
|
||||
const li = $this.closest('li');
|
||||
const column = TABLE.column(li.attr('data-position'));
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
if (column.header.id !== 'row-name') {
|
||||
if (!['row-name', 'archived-by', 'archived-on'].includes(column.header().id)) {
|
||||
if (column.visible()) {
|
||||
self.addClass('sn-icon-visibility-hide');
|
||||
self.removeClass('sn-icon-visibility-show');
|
||||
$this.addClass('sn-icon-visibility-hide');
|
||||
$this.removeClass('sn-icon-visibility-show');
|
||||
li.addClass('col-invisible');
|
||||
column.visible(false);
|
||||
TABLE.setColumnSearchable(column.index(), false);
|
||||
} else {
|
||||
self.addClass('sn-icon-visibility-show');
|
||||
self.removeClass('sn-icon-visibility-hide');
|
||||
$this.addClass('sn-icon-visibility-show');
|
||||
$this.removeClass('sn-icon-visibility-hide');
|
||||
li.removeClass('col-invisible');
|
||||
column.visible(true);
|
||||
TABLE.setColumnSearchable(column.index(), true);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global TinyMCE Results Comments animateSpinner initFormSubmitLinks */
|
||||
/* global TinyMCE Results Comments animateSpinner initFormSubmitLinks Prism */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
@ -82,6 +82,7 @@
|
|||
TinyMCE.destroyAll();
|
||||
Comments.init();
|
||||
initNewReslutText();
|
||||
Prism.highlightAllUnder(newResult.get(0));
|
||||
});
|
||||
$form.on('ajax:error', function(e, xhr) {
|
||||
var data = xhr.responseJSON;
|
||||
|
|
|
@ -30,3 +30,16 @@ var ActiveStoragePreviews = (function() {
|
|||
}
|
||||
});
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
$('.asset-preview-image')
|
||||
.one('load', (event) => ActiveStoragePreviews.showPreview(event))
|
||||
.one('error', (event) => ActiveStoragePreviews.reCheckPreview(event))
|
||||
.each(function() {
|
||||
if (this.complete) {
|
||||
$(this).load();
|
||||
} else if (this.error) {
|
||||
$(this).error();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -136,7 +136,9 @@ var dropdownSelector = (function() {
|
|||
if (selector.data('select-by-group')) {
|
||||
$.each(container.find('.dropdown-group'), function(gi, group) {
|
||||
if ($(group).find('.dropdown-option').length === $(group).find('.dropdown-option.select').length) {
|
||||
$(group).addClass('select');
|
||||
$(group).find('.group-name').addClass('select');
|
||||
} else {
|
||||
$(group).find('.group-name').removeClass('select');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -619,7 +621,7 @@ var dropdownSelector = (function() {
|
|||
// Disable group select to single select
|
||||
if (selector.data('config').singleSelect) return;
|
||||
|
||||
if (groupContainer.toggleClass('select').hasClass('select')) {
|
||||
if ($(this).toggleClass('select').hasClass('select')) {
|
||||
groupContainer.find('.dropdown-option').addClass('select');
|
||||
} else {
|
||||
groupContainer.find('.dropdown-option').removeClass('select');
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
.column-grip {
|
||||
color: $color-volcano;
|
||||
cursor: grab;
|
||||
cursor: grabbing;
|
||||
display: block;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
|
|
|
@ -419,6 +419,7 @@
|
|||
|
||||
.task-notes-content {
|
||||
margin-left: 10px;
|
||||
overflow-x: auto;
|
||||
|
||||
.form-group.has-error {
|
||||
border: 1px solid $brand-danger;
|
||||
|
|
|
@ -72,6 +72,10 @@
|
|||
margin-left: 4.25em !important;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
@include font-main;
|
||||
background: $color-concrete;
|
||||
|
|
|
@ -1,23 +1,44 @@
|
|||
.sci--layout-navigation-breadcrumbs {
|
||||
--max-breadcrumbs-link-width: 11.25rem;
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
column-gap: .875rem;
|
||||
background-color: var(--sn-white);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.breadcrumbs-container {
|
||||
@include font-small;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
max-width: calc(100vw - var(--left-navigation-width) - 3rem);
|
||||
overflow: hidden;
|
||||
width: calc(100vw - var(--left-navigation-width) - 3rem);
|
||||
|
||||
.delimiter {
|
||||
@include font-button;
|
||||
color: var(--sn-grey);
|
||||
font-weight: bold;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs-item {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.breadcrumbs-link {
|
||||
@include font-small;
|
||||
color: var(--sn-blue);
|
||||
display: inline-block;
|
||||
max-width: var(--max-breadcrumbs-link-width);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child {
|
||||
color: var(--sn-grey);
|
||||
&.shortened {
|
||||
max-width: var(--max-breadcrumbs-link-width);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.plain-text {
|
||||
|
@ -25,24 +46,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.delimiter {
|
||||
padding-bottom: .25rem;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumbs-collapsed-container {
|
||||
color: var(--sn-blue);
|
||||
position: relative;
|
||||
|
||||
.show-breadcrumbs {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
@include font-button;
|
||||
color: var(--sn-blue);
|
||||
}
|
||||
&:last-child .breadcrumbs-link {
|
||||
color: var(--sn-grey);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs-collapsed-container .breadcrumbs-item .breadcrumbs-link {
|
||||
color: var(--sn-blue);
|
||||
}
|
||||
|
|
|
@ -386,7 +386,6 @@ path, ._jsPlumb_endpoint {
|
|||
div {
|
||||
font-size: 20px;
|
||||
width: 4px;
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
|
||||
& .fas {
|
||||
|
|
|
@ -273,6 +273,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
#protocol-description-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.delete-steps-modal {
|
||||
.btn {
|
||||
float: initial;
|
||||
|
|
|
@ -109,7 +109,6 @@
|
|||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
height: calc(100% - var(--datatable-pagination-row) - var(--repository-top-toolbar-height));
|
||||
z-index: 1;
|
||||
|
||||
.dataTables_scrollHead {
|
||||
z-index: 90;
|
||||
|
@ -445,7 +444,7 @@
|
|||
background: $color-white;
|
||||
color: $color-silver-chalice;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
z-index: 1;
|
||||
|
||||
.repository-save-changes-link {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -81,7 +81,11 @@
|
|||
}
|
||||
|
||||
// Cells
|
||||
|
||||
td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Assigned
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
height: 3.5rem;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 1rem;
|
||||
z-index: 99;
|
||||
|
||||
|
||||
.view-switch,
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
}
|
||||
|
||||
.control {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
|
@ -51,7 +53,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.fas-check {
|
||||
.sn-icon-check {
|
||||
margin-left: .25em;
|
||||
}
|
||||
}
|
||||
|
@ -62,3 +64,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel.panel-default {
|
||||
.pull-right {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.sn-icon-check {
|
||||
margin-left: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,10 @@
|
|||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.filter-table input {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.filter-table,
|
||||
.display-limit {
|
||||
flex-shrink: 0;
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
.breadcrumbs-container {
|
||||
@include font-small;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.delimiter {
|
||||
@include font-button;
|
||||
color: $color-silver-chalice;
|
||||
font-weight: bold;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
.breadcrumbs-link {
|
||||
display: inline-block;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs-collapsed-container {
|
||||
color: $brand-primary;
|
||||
position: relative;
|
||||
|
||||
.show-breadcrumbs {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
@include font-button;
|
||||
color: $brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
.sci-input-container {
|
||||
.help-block {
|
||||
color: $brand-danger;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.sci-input-field[type=password] {
|
||||
font-family: Arial;
|
||||
letter-spacing: .075em;
|
||||
}
|
||||
|
||||
.sn-icon {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
}
|
||||
|
||||
.view-text-element {
|
||||
overflow-x: auto;
|
||||
pointer-events: initial;
|
||||
|
||||
a {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ module ActiveStorage
|
|||
|
||||
current_user.permission_team = asset.team || current_team
|
||||
|
||||
return true if !asset.saved && can_read_team?(asset.team)
|
||||
return true if asset.object.nil? && can_read_team?(asset.team)
|
||||
|
||||
case asset.object_type
|
||||
when 'MyModule'
|
||||
|
|
|
@ -41,10 +41,6 @@ class GlobalActivitiesController < ApplicationController
|
|||
@teams.order(name: :asc)
|
||||
end
|
||||
@activity_types = Activity.activity_types_list
|
||||
@user_list = User.where(id: UserTeam.where(team: current_user.teams).select(:user_id))
|
||||
.distinct
|
||||
.order(full_name: :asc)
|
||||
.pluck(:full_name, :id)
|
||||
|
||||
activities = ActivitiesService.load_activities(current_user, selected_teams, activity_filters)
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ class MyModuleTagsController < ApplicationController
|
|||
render json: {
|
||||
html_module_header: render_to_string(
|
||||
partial: 'my_modules/tags',
|
||||
locals: { my_module: @my_module, editable: can_manage_my_module?(@my_module) }
|
||||
locals: { my_module: @my_module, editable: can_manage_my_module?(@my_module) },
|
||||
formats: :html
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -36,7 +37,8 @@ class MyModuleTagsController < ApplicationController
|
|||
id: my_module.id,
|
||||
tags_html: render_to_string(
|
||||
partial: 'canvas/tags',
|
||||
locals: { my_module: my_module }
|
||||
locals: { my_module: my_module },
|
||||
formats: :html
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -115,9 +115,7 @@ class MyModulesController < ApplicationController
|
|||
MyModule: [@my_module.id]
|
||||
}
|
||||
@activity_types = Activity.activity_types_list
|
||||
@user_list = User.where(id: UserTeam.where(team: current_user.teams).select(:user_id))
|
||||
.distinct
|
||||
.pluck(:full_name, :id)
|
||||
|
||||
activities = ActivitiesService.load_activities(current_user, current_team, activity_filters)
|
||||
|
||||
@grouped_activities = activities.group_by do |activity|
|
||||
|
|
|
@ -109,20 +109,21 @@ module Navigator
|
|||
else
|
||||
object&.project_folder
|
||||
end
|
||||
has_children_sql = 'SUM(CASE WHEN viewable_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL
|
||||
THEN 1 ELSE 0 END) > 0'
|
||||
current_team.project_folders.where(parent_folder: folder)
|
||||
.where(project_folders: { archived: archived })
|
||||
.left_outer_joins(:projects, project_folders: {})
|
||||
.joins(
|
||||
"LEFT OUTER JOIN (#{Project.viewable_by_user(current_user, current_team).to_sql}) " \
|
||||
"LEFT OUTER JOIN (#{Project.viewable_by_user(current_user, current_team).where(archived: archived).to_sql}) " \
|
||||
"viewable_projects ON viewable_projects.project_folder_id = project_folders.id"
|
||||
)
|
||||
.select(
|
||||
'project_folders.id',
|
||||
'project_folders.name',
|
||||
'project_folders.archived',
|
||||
'SUM(CASE WHEN viewable_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL
|
||||
THEN 1 ELSE 0 END) > 0 AS has_children'
|
||||
"#{has_children_sql} AS has_children"
|
||||
).group('project_folders.id')
|
||||
.having("project_folders.archived = :archived OR #{has_children_sql}", archived: archived)
|
||||
end
|
||||
|
||||
def fetch_experiments(object, archived = false)
|
||||
|
|
|
@ -175,9 +175,7 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
def rename_modal
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'rename_repository_modal'
|
||||
)
|
||||
html: render_to_string(partial: 'rename_repository_modal', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -200,7 +198,7 @@ class RepositoriesController < ApplicationController
|
|||
name: @repository.name
|
||||
)
|
||||
render json: {
|
||||
html: render_to_string(partial: 'copy_repository_modal')
|
||||
html: render_to_string(partial: 'copy_repository_modal', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -210,6 +208,7 @@ class RepositoriesController < ApplicationController
|
|||
html: render_to_string(
|
||||
partial: 'export_repositories_modal',
|
||||
locals: { team_name: current_team.name,
|
||||
counter: params[:counter].to_i,
|
||||
export_limit: TeamZipExport.exports_limit,
|
||||
num_of_requests_left: current_user.exports_left - 1 },
|
||||
formats: :html
|
||||
|
@ -288,7 +287,7 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
if (@temp_file = parsed_file.generate_temp_file)
|
||||
render json: {
|
||||
html: render_to_string(partial: 'repositories/parse_records_modal')
|
||||
html: render_to_string(partial: 'repositories/parse_records_modal', formats: :html)
|
||||
}
|
||||
else
|
||||
repository_response(t('repositories.parse_sheet.errors.temp_file_failure'))
|
||||
|
|
|
@ -103,9 +103,9 @@ class ResultAssetsController < ApplicationController
|
|||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'my_modules/result', locals: { result: @result }
|
||||
partial: 'my_modules/result', locals: { result: @result }, formats: :html
|
||||
)
|
||||
}, status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
|
|
|
@ -54,7 +54,7 @@ class UserMyModulesController < ApplicationController
|
|||
render json: {
|
||||
user: {
|
||||
id: @um.user.id,
|
||||
full_name: @um.user.full_name,
|
||||
full_name: escape_input(@um.user.full_name),
|
||||
avatar_url: avatar_path(@um.user, :icon_small),
|
||||
user_module_id: @um.id
|
||||
}
|
||||
|
|
|
@ -150,8 +150,7 @@ module Users
|
|||
.distinct
|
||||
teams = teams.where_attributes_like('teams.name', params[:query]) if params[:query].present?
|
||||
|
||||
teams.select { |team| can_invite_team_users?(team) }
|
||||
|
||||
teams = teams.select { |team| can_invite_team_users?(team) }
|
||||
render json: teams.map { |t| { value: t.id, label: escape_input(t.name) } }.to_json
|
||||
end
|
||||
|
||||
|
|
|
@ -131,9 +131,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
@team.created_by = resource # set created_by for oraganization
|
||||
@team.save
|
||||
|
||||
# Add this user to the team as owner
|
||||
UserTeam.create(user: resource, team: @team, role: :admin)
|
||||
|
||||
# set current team to new user
|
||||
resource.current_team_id = @team.id
|
||||
resource.save
|
||||
|
@ -171,9 +168,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
@team.created_by = @user # set created_by for team
|
||||
@team.save!
|
||||
|
||||
# Add this user to the team as owner
|
||||
UserTeam.create(user: @user, team: @team, role: :admin)
|
||||
|
||||
# set current team to new user
|
||||
@user.current_team_id = @team.id
|
||||
@user.save!
|
||||
|
|
|
@ -107,7 +107,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
|
||||
def remove_authenticate_mesasge_if_root_path
|
||||
if session[:user_return_to] == root_path && flash[:alert] == I18n.t('devise.failure.unauthenticated')
|
||||
flash[:alert] = nil
|
||||
flash.clear
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -57,9 +57,7 @@ module Users
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@user_team = UserTeam.find_by(user: @user, team: @team)
|
||||
end
|
||||
def show; end
|
||||
|
||||
def users_datatable
|
||||
render json: ::TeamUsersDatatable.new(view_context, @team, @user)
|
||||
|
|
|
@ -100,15 +100,15 @@ class WopiController < ActionController::Base
|
|||
if @asset.lock == lock
|
||||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
else
|
||||
@asset.lock_asset(lock)
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -126,14 +126,14 @@ class WopiController < ActionController::Base
|
|||
@asset.unlock
|
||||
@asset.lock_asset(lock)
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -151,15 +151,15 @@ class WopiController < ActionController::Base
|
|||
create_wopi_file_activity(@user, false)
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
else
|
||||
logger.warn 'WOPI: tried to unlock non-locked file'
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -174,14 +174,14 @@ class WopiController < ActionController::Base
|
|||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -189,7 +189,7 @@ class WopiController < ActionController::Base
|
|||
def get_lock
|
||||
@asset.with_lock do
|
||||
response.headers['X-WOPI-Lock'] = @asset.locked? ? @asset.lock : ' '
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -211,11 +211,11 @@ class WopiController < ActionController::Base
|
|||
@protocol&.update(updated_at: Time.now.utc)
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
logger.warn 'WOPI: wrong lock used to try and modify file'
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
elsif !@asset.file_size.nil? && @asset.file_size.zero?
|
||||
logger.warn 'WOPI: initializing empty file'
|
||||
|
@ -227,11 +227,11 @@ class WopiController < ActionController::Base
|
|||
@team.save
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
return render body: nil, status: :ok
|
||||
render body: nil, status: :ok
|
||||
else
|
||||
logger.warn 'WOPI: trying to modify unlocked file'
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
return render body: nil, status: :conflict
|
||||
render body: nil, status: :conflict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -249,13 +249,13 @@ class WopiController < ActionController::Base
|
|||
@assoc = result_assoc unless result_assoc.nil?
|
||||
@assoc = repository_cell_assoc unless repository_cell_assoc.nil?
|
||||
|
||||
if @assoc.class == Step
|
||||
if @assoc.instance_of?(Step)
|
||||
@protocol = @asset.step.protocol
|
||||
@team = @protocol.team
|
||||
elsif @assoc.class == Result
|
||||
elsif @assoc.instance_of?(Result)
|
||||
@my_module = @assoc.my_module
|
||||
@team = @my_module.experiment.project.team
|
||||
elsif @assoc.class == RepositoryCell
|
||||
elsif @assoc.instance_of?(RepositoryCell)
|
||||
@repository = @assoc.repository_column.repository
|
||||
@team = @repository.team
|
||||
end
|
||||
|
@ -278,8 +278,9 @@ class WopiController < ActionController::Base
|
|||
|
||||
# This is what we get for settings permission methods with
|
||||
# current_user
|
||||
@user.permission_team = @team
|
||||
@current_user = @user
|
||||
if @assoc.class == Step
|
||||
if @assoc.instance_of?(Step)
|
||||
if @protocol.in_module?
|
||||
@can_read = can_read_protocol_in_module?(@protocol)
|
||||
@can_write = can_manage_step?(@assoc)
|
||||
|
@ -299,7 +300,7 @@ class WopiController < ActionController::Base
|
|||
@breadcrumb_folder_name = 'Protocol managament'
|
||||
end
|
||||
@breadcrumb_folder_url = @close_url
|
||||
elsif @assoc.class == Result
|
||||
elsif @assoc.instance_of?(Result)
|
||||
@can_read = can_read_experiment?(@my_module.experiment)
|
||||
@can_write = can_manage_my_module?(@my_module)
|
||||
|
||||
|
@ -311,7 +312,7 @@ class WopiController < ActionController::Base
|
|||
host: ENV['WOPI_USER_HOST'])
|
||||
@breadcrumb_folder_name = @my_module.name
|
||||
@breadcrumb_folder_url = @close_url
|
||||
elsif @assoc.class == RepositoryCell
|
||||
elsif @assoc.instance_of?(RepositoryCell)
|
||||
@can_read = can_read_repository?(@repository)
|
||||
@can_write = !@repository.is_a?(RepositorySnapshot) && can_edit_wopi_file_in_repository_rows?
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class ProtocolLinkedChildrenDatatable < CustomDatatable
|
|||
res += "<li><span class='sn-icon sn-icon-projects'></span> "
|
||||
res += @controller.render_to_string(
|
||||
partial: 'search/results/partials/project_text',
|
||||
locals: { project: record.my_module.experiment.project },
|
||||
locals: { project: record.my_module.experiment.project, link_to_page: :show },
|
||||
formats: :html
|
||||
)
|
||||
res += '</li>'
|
||||
|
|
|
@ -28,11 +28,11 @@ module ApplicationHelper
|
|||
|
||||
if message.strip.length > len
|
||||
sanitize_input("<div class='modal-tooltip'> \
|
||||
#{truncate(message.strip, length: len)} \
|
||||
#{message.strip} \
|
||||
<span class='modal-tooltiptext'> \
|
||||
#{message.strip}</span></div>")
|
||||
else
|
||||
truncate(message.strip, length: len)
|
||||
message.strip
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -68,6 +68,17 @@ module FileIconsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def sn_icon_for(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
'file-word'
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
'file-excel'
|
||||
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
|
||||
'file-powerpoint'
|
||||
end
|
||||
end
|
||||
|
||||
# For showing in view/edit buttons (WOPI)
|
||||
def file_application_icon(asset)
|
||||
image_link = file_application_url(asset)
|
||||
|
|
2
app/javascript/packs/tiny_mce.js
vendored
2
app/javascript/packs/tiny_mce.js
vendored
|
@ -178,7 +178,7 @@ window.TinyMCE = (() => {
|
|||
// Hide element containing HTML view of RTE field
|
||||
const tinyMceContainer = $(selector).closest('form').find('.tinymce-view');
|
||||
const tinyMceInitSize = tinyMceContainer.height();
|
||||
$(selector).closest('.form-group')
|
||||
$(selector).closest('.form-group, .tinymce-editor-container')
|
||||
.before(`<div class="tinymce-placeholder" style="height:${tinyMceInitSize}px"></div>`);
|
||||
tinyMceContainer.addClass('hidden');
|
||||
const plugins = `
|
||||
|
|
35
app/javascript/packs/vue/directives/outside_click.js
Normal file
35
app/javascript/packs/vue/directives/outside_click.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Use this to register outside-click directive on a Vue component
|
||||
// eslint-disable-next-line max-len
|
||||
// eg v-click-outside="{handler: 'handlerToTrigger', exclude: [refs to ignore on click (eg 'searchInput', 'searchInputBtn')]}"
|
||||
// eslint-enable-next-line max-len
|
||||
|
||||
let handleOutsideClick;
|
||||
|
||||
export default {
|
||||
bind(el, binding, vnode) {
|
||||
const { handler, exclude } = binding.value;
|
||||
|
||||
handleOutsideClick = (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
let clickedOnExcludedEl = false;
|
||||
exclude.forEach(refName => {
|
||||
if (!clickedOnExcludedEl) {
|
||||
const excludedEl = vnode.context.$refs[refName];
|
||||
clickedOnExcludedEl = excludedEl?.contains(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
if (!el.contains(e.target) && !clickedOnExcludedEl) {
|
||||
vnode.context[handler]();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
document.addEventListener('touchstart', handleOutsideClick);
|
||||
},
|
||||
unbind() {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
document.removeEventListener('touchstart', handleOutsideClick);
|
||||
}
|
||||
};
|
17
app/javascript/packs/vue/navigation/breadcrumbs.js
Normal file
17
app/javascript/packs/vue/navigation/breadcrumbs.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import TurbolinksAdapter from 'vue-turbolinks';
|
||||
import Vue from 'vue/dist/vue.esm';
|
||||
import Breadcrumbs from '../../../vue/navigation/breadcrumbs/breadcrumbs.vue';
|
||||
import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
||||
|
||||
Vue.use(TurbolinksAdapter);
|
||||
Vue.use(PerfectScrollbar);
|
||||
Vue.prototype.i18n = window.I18n;
|
||||
|
||||
window.breadcrumbsComponent = new Vue({
|
||||
el: '#breadcrumbs',
|
||||
name: 'BreadcrumbsContainer',
|
||||
components: {
|
||||
breadcrumbs: Breadcrumbs
|
||||
}
|
||||
});
|
|
@ -18,7 +18,6 @@ function initPrintModalComponent() {
|
|||
return {
|
||||
showModal: false,
|
||||
row_ids: [],
|
||||
zebraEnabled: container.data('zebra-enabled'),
|
||||
urls: {
|
||||
print: container.data('print-url'),
|
||||
zebraProgress: container.data('zebra-progress-url'),
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import TurbolinksAdapter from 'vue-turbolinks';
|
||||
import Vue from 'vue/dist/vue.esm';
|
||||
import RepositorySearchContainer from '../../vue/repository_search/container.vue';
|
||||
import outsideClick from './directives/outside_click';
|
||||
|
||||
Vue.use(TurbolinksAdapter);
|
||||
Vue.prototype.i18n = window.I18n;
|
||||
Vue.directive('click-outside', outsideClick);
|
||||
|
||||
window.initRepositorySearch = () => {
|
||||
window.RepositorySearchComponent = new Vue({
|
||||
|
|
162
app/javascript/vue/navigation/breadcrumbs/breadcrumbs.vue
Normal file
162
app/javascript/vue/navigation/breadcrumbs/breadcrumbs.vue
Normal file
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div class="breadcrumbs-container" ref="container">
|
||||
<a
|
||||
v-if="firstItem && firstItem.url !== lastItem.url"
|
||||
:href="firstItem.url"
|
||||
class="breadcrumbs-item"
|
||||
:title="firstItem.label"
|
||||
ref="firstItem"
|
||||
>
|
||||
<span
|
||||
class="breadcrumbs-link"
|
||||
:class="{
|
||||
shortened:
|
||||
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED,
|
||||
'plain-text': !firstItem.url
|
||||
}"
|
||||
>{{ firstItem.label }}</span>
|
||||
<span class="delimiter">
|
||||
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
|
||||
</span>
|
||||
</a>
|
||||
<template v-if="middleItems.length">
|
||||
<BreadcrumbsDropdown
|
||||
v-if="hiddenMiddleItems.length"
|
||||
:items="hiddenMiddleItems"
|
||||
:delimiterUrl="delimiterUrl"
|
||||
/>
|
||||
<a
|
||||
v-for="(item, index) in visibleMiddleItems"
|
||||
:key="item.url"
|
||||
:href="item.url"
|
||||
class="breadcrumbs-item"
|
||||
:title="item.label"
|
||||
:ref="`visibleMiddleItem-${index}`"
|
||||
>
|
||||
<span
|
||||
class="breadcrumbs-link"
|
||||
:class="{
|
||||
shortened:
|
||||
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED
|
||||
}"
|
||||
>{{ item.label }}</span
|
||||
>
|
||||
<span class="delimiter">
|
||||
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
<span class="breadcrumbs-item" title="lastItem.label" ref="lastItem">
|
||||
<span
|
||||
class="breadcrumbs-link"
|
||||
:title="lastItem.label"
|
||||
:class="{
|
||||
shortened:
|
||||
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED
|
||||
}"
|
||||
>{{ lastItem.label }}</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BreadcrumbsDropdown from "./breadcrumbs_dropdown.vue";
|
||||
|
||||
const State = Object.freeze({
|
||||
INITIAL: "initial",
|
||||
EXPENDED: "expended",
|
||||
SHORTENED: "shortened",
|
||||
SHORTENED_COLLAPSED: "shortened_collapsed"
|
||||
});
|
||||
const dropdownWidth = 60;
|
||||
export default {
|
||||
name: "Breadcrumbs",
|
||||
props: {
|
||||
breadcrumbsItems: String,
|
||||
delimiterUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownWidth: dropdownWidth,
|
||||
State,
|
||||
state: State.INITIAL,
|
||||
items: [],
|
||||
hiddenMiddleItems: [],
|
||||
visibleMiddleItems: []
|
||||
};
|
||||
},
|
||||
components: {
|
||||
BreadcrumbsDropdown
|
||||
},
|
||||
watch: {
|
||||
breadcrumbsItems: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.items = JSON.parse(this.breadcrumbsItems);
|
||||
this.reset();
|
||||
this.$nextTick(() => {
|
||||
this.updateItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
firstItem() {
|
||||
if (this.items.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
return this.items[0];
|
||||
},
|
||||
lastItem() {
|
||||
return this.items[this.items.length - 1];
|
||||
},
|
||||
middleItems() {
|
||||
if (this.items.length <= 2) return [];
|
||||
|
||||
return this.items.slice(1, -1);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateItems() {
|
||||
const width = this.$refs.container.clientWidth;
|
||||
const scrollWidth = Array.from(this.$refs.container.children).reduce(
|
||||
(totalWidth, child) => totalWidth + child.offsetWidth,
|
||||
0
|
||||
);
|
||||
if (this.state === this.State.INITIAL) {
|
||||
if (width < scrollWidth) {
|
||||
this.state = this.State.SHORTENED;
|
||||
this.$nextTick(() => {
|
||||
this.updateItems();
|
||||
});
|
||||
} else {
|
||||
this.state = this.State.EXPENDED;
|
||||
}
|
||||
} else if (this.state === this.State.SHORTENED) {
|
||||
if (width < scrollWidth) {
|
||||
this.state = this.State.SHORTENED_COLLAPSED;
|
||||
let visibleWidth =
|
||||
this.dropdownWidth +
|
||||
this.$refs.firstItem.offsetWidth +
|
||||
this.$refs.lastItem.offsetWidth;
|
||||
let index = this.middleItems.length - 1;
|
||||
|
||||
while (visibleWidth <= width && index >= 0) {
|
||||
visibleWidth += this.$refs[`visibleMiddleItem-${index}`][0]
|
||||
.offsetWidth;
|
||||
index--;
|
||||
}
|
||||
this.visibleMiddleItems = this.middleItems.slice(index + 2);
|
||||
this.hiddenMiddleItems = this.middleItems.slice(0, index + 2);
|
||||
}
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.state = this.State.INITIAL;
|
||||
this.hiddenMiddleItems = [];
|
||||
this.visibleMiddleItems = this.middleItems;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<span class="breadcrumbs-collapsed-container">
|
||||
<span
|
||||
class="cursor-pointer text-sn-blue flex flex-nowrap whitespace-nowrap items-center gap-1"
|
||||
data-toggle="dropdown"
|
||||
:title="i18n.t('projects.index.breadcrumbs_collapsed')"
|
||||
@click="open = !open"
|
||||
>
|
||||
•••
|
||||
<span class="caret"></span>
|
||||
<span class="delimiter">
|
||||
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
|
||||
</span>
|
||||
</span>
|
||||
<perfect-scrollbar>
|
||||
<ul
|
||||
class="absolute top-11 left-0 w-56 max-h-36 flex flex-col items-stretch gap-2 overflow-auto
|
||||
bg-sn-white border border-sn-grey rounded-md shadow-md list-none p-2"
|
||||
:class="{ hidden: !open }"
|
||||
>
|
||||
<li v-for="item in items" :key="item.url">
|
||||
<a :href="item.url" class="breadcrumbs-item breadcrumb-link shortened" title="item.label">
|
||||
<span
|
||||
class="breadcrumbs-link shortened"
|
||||
>{{ item.label }}</span>
|
||||
<span class="delimiter">
|
||||
<img
|
||||
:src="delimiterUrl"
|
||||
alt="navigate next"
|
||||
class="navigate_next"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</perfect-scrollbar>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
|
||||
|
||||
export default {
|
||||
name: "BreadcrumbsDropdown",
|
||||
props: {
|
||||
items: Array,
|
||||
delimiterUrl: String
|
||||
},
|
||||
components: { PerfectScrollbar },
|
||||
data() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -63,9 +63,14 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
hasChildren: function() {
|
||||
return !this.item.disabled && (this.item.has_children || this.children.length > 0);
|
||||
if (this.item.disabled) return false;
|
||||
if (this.item.has_children) return true;
|
||||
if (this.children && this.children.length > 0) return true;
|
||||
return false
|
||||
},
|
||||
sortedMenuItems: function() {
|
||||
if (!this.children) return [];
|
||||
|
||||
return this.children.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||
return -1;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<a
|
||||
data-toggle="modal"
|
||||
data-target="#newProtocolModal"
|
||||
v-bind:data-protocol-name="protocol.attributes.name"
|
||||
:class="{ disabled: !protocol.attributes.urls.save_to_repo_url }"
|
||||
>
|
||||
<span class="fas fa-save"></span>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
:title="attachment.attributes.wopi_context.title"
|
||||
target="_blank"
|
||||
>
|
||||
<img :src="attachment.attributes.wopi_context.wopi_icon"/>
|
||||
<i :class="`sn-icon sn-icon-${attachment.attributes.wopi_context.sn_icon}`"></i>
|
||||
{{ attachment.attributes.wopi_context.button_text }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* global HelperModule */
|
||||
|
||||
const FILENAME_MAX_LENGTH = 100;
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
initWopiFileModal(step, requestCallback) {
|
||||
|
@ -10,6 +12,13 @@ export default {
|
|||
$wopiModal.modal('show');
|
||||
$($wopiModal).find('#new-wopi-file-name').focus();
|
||||
|
||||
// Clear filename input error on input change if appropriate
|
||||
$wopiModal.on('input', '#new-wopi-file-name', (e) => {
|
||||
if (e.currentTarget.value.length <= FILENAME_MAX_LENGTH) {
|
||||
$wopiModal.clearFormErrors();
|
||||
}
|
||||
});
|
||||
|
||||
$wopiModal.find('form').on(
|
||||
'ajax:success',
|
||||
(e, data, status) => {
|
||||
|
|
|
@ -99,8 +99,7 @@
|
|||
props: {
|
||||
showModal: Boolean,
|
||||
row_ids: Array,
|
||||
urls: Object,
|
||||
zebraEnabled: Boolean
|
||||
urls: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -130,12 +129,9 @@
|
|||
})
|
||||
|
||||
$(this.$refs.modal).on('hidden.bs.modal', () => {
|
||||
this.zebraPrinters = null;
|
||||
this.$emit('close');
|
||||
});
|
||||
|
||||
if (this.zebraEnabled) {
|
||||
this.initZebraPrinter();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
availableTemplates() {
|
||||
|
@ -167,6 +163,7 @@
|
|||
watch: {
|
||||
showModal() {
|
||||
if (this.showModal) {
|
||||
this.initZebraPrinter();
|
||||
$(this.$refs.modal).modal('show');
|
||||
this.validateTemplate();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div class="flex items-center mr-3 flex-nowrap">
|
||||
<button v-if="!searchOpened" class="btn btn-light btn-lg btn-black icon-btn" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
|
||||
<div
|
||||
class="flex items-center mr-3 flex-nowrap relative"
|
||||
v-click-outside="{handler: 'closeSearchInputs', exclude: ['searchInput', 'searchInputBtn', 'barcodeSearchInput', 'barcodeSearchInputBtn']}"
|
||||
>
|
||||
<button :class="{hidden: searchOpened}" ref='searchInputBtn' class="btn btn-light btn-lg btn-black icon-btn" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
</button>
|
||||
<div v-if="searchOpened || barcodeSearchOpened" class="w-52 flex">
|
||||
|
@ -11,7 +14,6 @@
|
|||
type="text"
|
||||
:placeholder="i18n.t('repositories.show.filter_inventory_items')"
|
||||
@keyup="setValue"
|
||||
@blur="closeSearch"
|
||||
/>
|
||||
<i class="sn-icon sn-icon-search !mr-2.5"></i>
|
||||
</div>
|
||||
|
@ -21,13 +23,12 @@
|
|||
class="sci-input-field"
|
||||
type="text"
|
||||
:placeholder="i18n.t('repositories.show.filter_inventory_items_with_ean')"
|
||||
@change="setBarcodeValue"
|
||||
@blur="closeBarcodeSearch"
|
||||
@keyup="setBarcodeValue"
|
||||
/>
|
||||
<i class='sn-icon sn-icon-barcode barcode-scanner !mr-2.5'></i>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!barcodeSearchOpened" class="btn btn-light btn-lg btn-black icon-btn ml-2" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
|
||||
<button :class="{hidden: barcodeSearchOpened}" ref='barcodeSearchInputBtn' class="btn btn-light btn-lg btn-black icon-btn ml-2" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
|
||||
<i class='sn-icon sn-icon-barcode barcode-scanner'></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -66,7 +67,7 @@ export default {
|
|||
},
|
||||
openBarcodeSearch() {
|
||||
this.clearValues();
|
||||
this.closeSearch();
|
||||
this.searchOpened = false;
|
||||
this.barcodeSearchOpened = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.barcodeSearchInput.focus();
|
||||
|
@ -74,7 +75,7 @@ export default {
|
|||
},
|
||||
openSearch() {
|
||||
this.clearValues();
|
||||
this.closeBarcodeSearch();
|
||||
this.barcodeSearchOpened = false;
|
||||
this.searchOpened = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchInput.focus();
|
||||
|
@ -103,6 +104,10 @@ export default {
|
|||
this.barcodeValue = '';
|
||||
if (this.$refs.searchInput) this.$refs.searchInput.value = '';
|
||||
if (this.$refs.barcodeSearchInput) this.$refs.barcodeSearchInput.value = '';
|
||||
},
|
||||
closeSearchInputs() {
|
||||
this.closeSearch();
|
||||
this.closeBarcodeSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
:data-placeholder="placeholder"
|
||||
:data-tinymce-init="`tinymce-${objectType}-description-${objectId}`">
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="flex tinymce-editor-container">
|
||||
<textarea :id="`${objectType}_textarea_${objectId}`"
|
||||
class="form-control hidden"
|
||||
autocomplete="off"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationSettings < Settings
|
||||
def load_values_from_env
|
||||
ENV.select { |name, _| name =~ /^APP_STTG_[A-Z0-9_]*/ }.transform_keys(&:downcase)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,10 +19,6 @@ class LabelPrinter < ApplicationRecord
|
|||
validates :type_of, presence: true
|
||||
validates :language_type, presence: true
|
||||
|
||||
def self.zebra_print_enabled?
|
||||
RepositoryBase.stock_management_enabled?.present?
|
||||
end
|
||||
|
||||
def done?
|
||||
current_print_job_ids.blank? && ready?
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ class LabelTemplate < ApplicationRecord
|
|||
validate :ensure_single_default_template!
|
||||
|
||||
def self.enabled?
|
||||
RepositoryBase.stock_management_enabled?
|
||||
ApplicationSettings.instance.values['label_templates_enabled'] == true
|
||||
end
|
||||
|
||||
def icon
|
||||
|
|
|
@ -172,19 +172,6 @@ class Project < ApplicationRecord
|
|||
ProjectComment.from(comments, :comments).order(created_at: :asc)
|
||||
end
|
||||
|
||||
def unassigned_users
|
||||
User.joins(:user_teams)
|
||||
.joins(
|
||||
"LEFT OUTER JOIN user_assignments ON user_assignments.user_id = users.id "\
|
||||
"AND user_assignments.assignable_id = #{id} "\
|
||||
"AND user_assignments.assignable_type = 'Project'"
|
||||
)
|
||||
.where(user_teams: { team_id: team_id })
|
||||
.where(user_assignments: { id: nil })
|
||||
.where.not(confirmed_at: nil)
|
||||
.distinct
|
||||
end
|
||||
|
||||
def user_role(user)
|
||||
user_assignments.includes(:user_role).references(:user_role).find_by(user: user)&.user_role&.name
|
||||
end
|
||||
|
@ -289,7 +276,7 @@ class Project < ApplicationRecord
|
|||
tables = parsed_html.css('.hot-table-contents')
|
||||
.zip(parsed_html.css('.hot-table-container'), parsed_html.css('.hot-table-metadata'))
|
||||
tables.each do |table_input, table_container, metadata|
|
||||
is_plate_template = JSON.parse(metadata['value'])['plateTemplate'] if metadata.present?
|
||||
is_plate_template = JSON.parse(metadata['value'])['plateTemplate'] if metadata && metadata['value'].present?
|
||||
table_vals = JSON.parse(table_input['value'])
|
||||
table_data = table_vals['data']
|
||||
table_headers = table_vals['headers']
|
||||
|
|
|
@ -122,7 +122,7 @@ class Report < ApplicationRecord
|
|||
)
|
||||
end
|
||||
|
||||
report = Report.new
|
||||
report = Report.new(skip_user_assignments: true)
|
||||
report.name = loop do
|
||||
dummy_name = SecureRandom.hex(10)
|
||||
break dummy_name unless Report.exists?(name: dummy_name)
|
||||
|
|
|
@ -4,4 +4,16 @@ class Settings < ApplicationRecord
|
|||
def self.instance
|
||||
first || new
|
||||
end
|
||||
|
||||
def values
|
||||
merged_values = super
|
||||
self.class.instance_methods(false).grep(/^load_values_from_[A-Z0-9_]*/).each do |method|
|
||||
merged_values = merged_values.merge(public_send(method))
|
||||
end
|
||||
merged_values
|
||||
end
|
||||
|
||||
def load_values_from_env
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
|
|
@ -105,6 +105,10 @@ class Table < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def metadata
|
||||
attributes['metadata'].is_a?(String) ? JSON.parse(attributes['metadata']) : attributes['metadata']
|
||||
end
|
||||
|
||||
def contents_utf_8
|
||||
contents.present? ? contents.force_encoding(Encoding::UTF_8) : nil
|
||||
end
|
||||
|
|
|
@ -31,7 +31,6 @@ class Team < ApplicationRecord
|
|||
foreign_key: 'last_modified_by_id',
|
||||
class_name: 'User',
|
||||
optional: true
|
||||
has_many :user_teams, inverse_of: :team, dependent: :destroy
|
||||
has_many :users, through: :user_assignments
|
||||
has_many :projects, inverse_of: :team
|
||||
has_many :project_folders, inverse_of: :team, dependent: :destroy
|
||||
|
@ -171,8 +170,8 @@ class Team < ApplicationRecord
|
|||
def self.global_activity_filter(filters, search_query)
|
||||
query = where('name ILIKE ?', "%#{search_query}%")
|
||||
if filters[:users]
|
||||
users_team = User.where(id: filters[:users]).joins(:user_teams).group(:team_id).pluck(:team_id)
|
||||
query = query.where(id: users_team)
|
||||
user_teams = User.where(id: filters[:users]).joins(:teams).group(:team_id).select(:team_id)
|
||||
query = query.where(id: user_teams)
|
||||
end
|
||||
query = query.where(id: team_by_subject(filters[:subjects])) if filters[:subjects]
|
||||
query.select(:id, :name).map { |i| { value: i[:id], label: ApplicationController.helpers.escape_input(i[:name]) } }
|
||||
|
|
|
@ -58,7 +58,6 @@ class User < ApplicationRecord
|
|||
|
||||
# Relations
|
||||
has_many :user_identities, inverse_of: :user
|
||||
has_many :user_teams, inverse_of: :user
|
||||
has_many :user_assignments, dependent: :destroy
|
||||
has_many :user_projects, inverse_of: :user
|
||||
has_many :teams, through: :user_assignments, source: :assignable, source_type: 'Team'
|
||||
|
@ -157,8 +156,8 @@ class User < ApplicationRecord
|
|||
|
||||
has_many :tokens,
|
||||
class_name: 'Token',
|
||||
foreign_key: 'user_id',
|
||||
inverse_of: :user
|
||||
inverse_of: :user,
|
||||
dependent: :destroy
|
||||
|
||||
has_many :modified_tags,
|
||||
class_name: 'Tag',
|
||||
|
@ -166,9 +165,6 @@ class User < ApplicationRecord
|
|||
has_many :assigned_user_my_modules,
|
||||
class_name: 'UserMyModule',
|
||||
foreign_key: 'assigned_by_id'
|
||||
has_many :assigned_user_teams,
|
||||
class_name: 'UserTeam',
|
||||
foreign_key: 'assigned_by_id'
|
||||
has_many :assigned_user_projects,
|
||||
class_name: 'UserProject',
|
||||
foreign_key: 'assigned_by_id'
|
||||
|
@ -311,7 +307,6 @@ class User < ApplicationRecord
|
|||
has_many :user_notifications, inverse_of: :user
|
||||
has_many :notifications, through: :user_notifications
|
||||
has_many :zip_exports, inverse_of: :user, dependent: :destroy
|
||||
has_many :datatables_teams, class_name: '::Views::Datatables::DatatablesTeam'
|
||||
has_many :view_states, dependent: :destroy
|
||||
|
||||
has_many :access_grants, class_name: 'Doorkeeper::AccessGrant',
|
||||
|
@ -434,46 +429,6 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def projects_by_teams(team_id = 0, sort_by = nil, archived = false)
|
||||
archived = archived ? true : false
|
||||
query = Project.all.joins(:user_projects)
|
||||
sql = 'projects.team_id IN (SELECT DISTINCT team_id ' \
|
||||
'FROM user_teams WHERE user_teams.user_id = :user_id)'
|
||||
if team_id.zero? || !user_teams.find_by(team_id: team_id).try(:admin?)
|
||||
# Admins see all projects of team
|
||||
sql += ' AND (projects.visibility=1 OR user_projects.user_id=:user_id)'
|
||||
end
|
||||
sql += ' AND projects.archived = :archived '
|
||||
|
||||
sort =
|
||||
case sort_by
|
||||
when 'old'
|
||||
{ created_at: :asc }
|
||||
when 'atoz'
|
||||
{ name: :asc }
|
||||
when 'ztoa'
|
||||
{ name: :desc }
|
||||
else
|
||||
{ created_at: :desc }
|
||||
end
|
||||
|
||||
if team_id > 0
|
||||
result = query
|
||||
.where('projects.team_id = ?', team_id)
|
||||
.where(sql, user_id: id, archived: archived)
|
||||
.order(sort)
|
||||
.distinct
|
||||
.group_by(&:team)
|
||||
else
|
||||
result = query
|
||||
.where(sql, user_id: id, archived: archived)
|
||||
.order(sort)
|
||||
.distinct
|
||||
.group_by(&:team)
|
||||
end
|
||||
result || []
|
||||
end
|
||||
|
||||
# Finds all activities of user that is assigned to project. If user
|
||||
# is not an owner of the project, user must be also assigned to
|
||||
# module.
|
||||
|
@ -499,11 +454,9 @@ class User < ApplicationRecord
|
|||
|
||||
def self.find_by_valid_wopi_token(token)
|
||||
Rails.logger.warn "WOPI: searching by token #{token}"
|
||||
User
|
||||
.joins('LEFT OUTER JOIN tokens ON user_id = users.id')
|
||||
.where(tokens: { token: token })
|
||||
.where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i)
|
||||
.first
|
||||
User.joins(:tokens)
|
||||
.where(tokens: { token: token })
|
||||
.find_by('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i)
|
||||
end
|
||||
|
||||
def get_wopi_token
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserTeam < ApplicationRecord
|
||||
enum role: { guest: 0, normal_user: 1, admin: 2 }
|
||||
|
||||
validates :role, :user, :team, presence: true
|
||||
|
||||
belongs_to :user, inverse_of: :user_teams, touch: true
|
||||
belongs_to :assigned_by, foreign_key: 'assigned_by_id', class_name: 'User', optional: true
|
||||
belongs_to :team, inverse_of: :user_teams
|
||||
|
||||
after_create :assign_user_to_visible_projects
|
||||
before_destroy :destroy_associations
|
||||
|
||||
def role_str
|
||||
I18n.t("user_teams.enums.role.#{role}")
|
||||
end
|
||||
|
||||
def destroy_associations
|
||||
# Destroy the user from all team's projects
|
||||
user.user_projects.joins(:project).where(project: team.projects).destroy_all
|
||||
# destroy all assignments
|
||||
UserAssignments::RemoveUserAssignmentJob.perform_now(user, team)
|
||||
end
|
||||
|
||||
# returns user_teams where the user is in team
|
||||
def self.user_in_team(user, team)
|
||||
where(user: user, team: team)
|
||||
end
|
||||
|
||||
def destroy(new_owner)
|
||||
return super() unless new_owner
|
||||
|
||||
# Also, make new owner author of all protocols that belong
|
||||
# to the departing user and belong to this team.
|
||||
p_ids = user.added_protocols.where(team: team).pluck(:id)
|
||||
Protocol.find(p_ids).each do |protocol|
|
||||
protocol.record_timestamps = false
|
||||
protocol.added_by = new_owner
|
||||
if protocol.archived_by != nil
|
||||
protocol.archived_by = new_owner
|
||||
end
|
||||
if protocol.restored_by != nil
|
||||
protocol.restored_by = new_owner
|
||||
end
|
||||
protocol.save
|
||||
end
|
||||
|
||||
# Make new owner author of all inventory items that were added
|
||||
# by departing user and belong to this team.
|
||||
RepositoryRow.change_owner(team, user, new_owner)
|
||||
|
||||
super()
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_user_to_visible_projects
|
||||
team.projects.visible.each do |project|
|
||||
UserAssignments::ProjectGroupAssignmentJob.perform_later(
|
||||
team,
|
||||
project,
|
||||
assigned_by
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -92,8 +92,7 @@ Canaid::Permissions.register_for(Repository) do
|
|||
|
||||
# repository: create/update/delete filters
|
||||
can :manage_repository_filters do |user, repository|
|
||||
((repository.team == user.current_team) && can_manage_team?(user, repository.team)) ||
|
||||
(repository.shared_with_write?(user.current_team) && can_manage_team?(user, user.current_team))
|
||||
repository.permission_granted?(user, RepositoryPermissions::FILTERS_MANAGE)
|
||||
end
|
||||
|
||||
can :manage_repository_stock do |user, repository|
|
||||
|
|
|
@ -8,7 +8,7 @@ module Api
|
|||
has_many :checklist_items, serializer: ChecklistItemSerializer
|
||||
|
||||
def position
|
||||
object.step_orderable_element.position
|
||||
object&.step_orderable_element&.position
|
||||
end
|
||||
|
||||
include TimestampableModel
|
||||
|
|
|
@ -13,7 +13,7 @@ module Api
|
|||
end
|
||||
|
||||
def position
|
||||
object.step_orderable_element.position
|
||||
object&.step_orderable_element&.position
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module Api
|
|||
end
|
||||
|
||||
def position
|
||||
object.step_table&.step_orderable_element&.position
|
||||
object&.step_table&.step_orderable_element&.position
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,7 +64,8 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
edit_supported: edit_supported,
|
||||
title: title,
|
||||
button_text: wopi_button_text(object, 'edit'),
|
||||
wopi_icon: ActionController::Base.helpers.image_path(file_application_url(object))
|
||||
wopi_icon: ActionController::Base.helpers.image_path(file_application_url(object)),
|
||||
sn_icon: sn_icon_for(object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,7 +86,7 @@ class ProjectsOverviewService
|
|||
|
||||
def fetch_project_records
|
||||
@team.projects
|
||||
.includes(user_assignments: %i(user user_role), team: :user_teams)
|
||||
.includes(:team, user_assignments: %i(user user_role))
|
||||
.includes(:project_comments, experiments: { my_modules: { my_module_status: :my_module_status_implications } })
|
||||
.visible_to(@user, @team)
|
||||
.left_outer_joins(:project_comments)
|
||||
|
@ -97,7 +97,7 @@ class ProjectsOverviewService
|
|||
|
||||
def fetch_project_folder_records
|
||||
project_folders = @team.project_folders
|
||||
.includes(team: :user_teams)
|
||||
.includes(:team)
|
||||
.joins('LEFT OUTER JOIN project_folders child_folders
|
||||
ON child_folders.parent_folder_id = project_folders.id')
|
||||
.left_outer_joins(:projects)
|
||||
|
|
|
@ -8,13 +8,27 @@ module Reports::Docx::DrawResultTable
|
|||
obj = self
|
||||
table_data = JSON.parse(table.contents_utf_8)['data']
|
||||
table_data = obj.add_headers_to_table(table_data, false)
|
||||
|
||||
if table.metadata.present?
|
||||
table.metadata['cells']&.each do |cell|
|
||||
next unless cell['row'].present? && cell['col'].present?
|
||||
|
||||
row_index = cell['row'].to_i + 1
|
||||
col_index = cell['col'].to_i + 1
|
||||
calculated_value = cell['calculated']
|
||||
|
||||
if calculated_value.present?
|
||||
table_data[row_index][col_index] = calculated_value
|
||||
end
|
||||
end
|
||||
end
|
||||
@docx.p
|
||||
@docx.table table_data, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE do
|
||||
cell_style rows[0], bold: true, background: color[:concrete]
|
||||
cell_style cols[0], bold: true, background: color[:concrete]
|
||||
|
||||
if table.metadata.present?
|
||||
JSON.parse(table.metadata)['cells']&.each do |cell|
|
||||
table.metadata['cells']&.each do |cell|
|
||||
next unless cell.present? && cell['row'].present? && cell['col'].present? && cell['className'].present?
|
||||
|
||||
cell_style rows.dig(cell['row'].to_i + 1, cell['col'].to_i + 1),
|
||||
|
|
|
@ -7,6 +7,20 @@ module Reports::Docx::DrawStepTable
|
|||
obj = self
|
||||
table_data = JSON.parse(table.contents_utf_8)['data']
|
||||
table_data = obj.add_headers_to_table(table_data, table_type == 'step_well_plates_table')
|
||||
|
||||
if table.metadata.present?
|
||||
table.metadata['cells']&.each do |cell|
|
||||
next unless cell['row'].present? && cell['col'].present?
|
||||
|
||||
row_index = cell['row'].to_i + 1
|
||||
col_index = cell['col'].to_i + 1
|
||||
calculated_value = cell['calculated']
|
||||
|
||||
if calculated_value.present?
|
||||
table_data[row_index][col_index] = calculated_value
|
||||
end
|
||||
end
|
||||
end
|
||||
@docx.p
|
||||
@docx.table table_data, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE do
|
||||
cell_style rows[0], bold: true, background: color[:concrete]
|
||||
|
|
|
@ -43,9 +43,9 @@ module SmartAnnotations
|
|||
def parse_users_annotations(user, team, text)
|
||||
@text = text.gsub(USER_REGEX) do |el|
|
||||
match = el.match(USER_REGEX)
|
||||
user = User.find_by_id(match[2].base62_decode)
|
||||
user = team.users.find_by(id: match[2].base62_decode)
|
||||
next unless user
|
||||
next if UserTeam.where(user: user, team: team).blank?
|
||||
|
||||
user.full_name
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,7 @@ module Toolbars
|
|||
label: I18n.t('libraries.index.buttons.export'),
|
||||
button_id: 'exportRepoBtn',
|
||||
icon: 'sn-icon sn-icon-export',
|
||||
path: export_modal_team_repositories_path(@current_team),
|
||||
path: export_modal_team_repositories_path(@current_team, counter: @repositories.length),
|
||||
type: 'remote-modal'
|
||||
}
|
||||
end
|
||||
|
|
|
@ -79,7 +79,7 @@ module Toolbars
|
|||
{
|
||||
name: 'assign',
|
||||
label: I18n.t('repositories.assign_record'),
|
||||
icon: 'sn-icon sn-icon-files',
|
||||
icon: 'sn-icon sn-icon-assign-to-task',
|
||||
button_class: 'assign-repository-rows-btn',
|
||||
button_id: 'assignRepositoryRecords',
|
||||
type: :legacy
|
||||
|
|
|
@ -74,7 +74,6 @@ class UserDataDeletion
|
|||
destroy_protocol(protocol)
|
||||
end
|
||||
team.protocol_keywords.destroy_all
|
||||
team.user_teams.delete_all
|
||||
User.where(current_team_id: team).each do |user|
|
||||
user.update(current_team_id: nil)
|
||||
end
|
||||
|
|
|
@ -43,10 +43,9 @@
|
|||
<% elsif asset.previewable? %>
|
||||
<div class="image-container">
|
||||
<%= image_tag asset.large_preview,
|
||||
class: 'asset-inline-image',
|
||||
class: 'asset-preview-image',
|
||||
style: 'opacity: 0' %>
|
||||
</div>
|
||||
<%= javascript_include_tag 'assets/asset_inline' %>
|
||||
<% else %>
|
||||
<div class="general-file-container">
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
||||
<% if asset.previewable? %>
|
||||
<%= image_tag asset.medium_preview,
|
||||
class: 'asset-thumbnail-image',
|
||||
class: 'asset-preview-image',
|
||||
style: 'opacity: 0' %>
|
||||
<%= javascript_include_tag 'assets/asset_thumbnail' %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
<% end %>
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
data-print-validation-url="<%= validate_label_template_columns_repository_rows_path %>"
|
||||
data-label-preview-url="<%= zpl_preview_label_templates_path %>"
|
||||
data-fluics-info-url="<%= Constants::SCINOTE_FLUICS_URL %>"
|
||||
data-zebra-enabled="<%= LabelPrinter.zebra_print_enabled? %>"
|
||||
>
|
||||
<print-modal-container
|
||||
:show-modal = "showModal"
|
||||
:row_ids = "row_ids"
|
||||
:urls = "urls"
|
||||
:zebra-enabled = "zebraEnabled"
|
||||
@close="closeModal"
|
||||
></print-modal-container>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
<% if LabelPrinter.zebra_print_enabled? %>
|
||||
<%= javascript_include_tag 'BrowserPrint-3.0.216.min' %>
|
||||
<%= javascript_include_tag 'BrowserPrint-Zebra-1.0.216.min' %>
|
||||
<% end %>
|
||||
<%= javascript_include_tag 'BrowserPrint-3.0.216.min' %>
|
||||
<%= javascript_include_tag 'BrowserPrint-Zebra-1.0.216.min' %>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<h1 class="printer-title">
|
||||
<% if @printer_type == 'fluics' %>
|
||||
<%= t("users.settings.account.label_printer.fluics_printer") %>
|
||||
<% elsif @printer_type == 'zebra' && LabelPrinter.zebra_print_enabled? %>
|
||||
<% elsif @printer_type == 'zebra' %>
|
||||
<%= t("users.settings.account.label_printer.zebra_printer") %>
|
||||
<% end %>
|
||||
</h1>
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
<% if @printer_type == 'fluics' %>
|
||||
<%= render "fluics_settings" %>
|
||||
<% elsif @printer_type == 'zebra' && LabelPrinter.zebra_print_enabled? %>
|
||||
<% elsif @printer_type == 'zebra' %>
|
||||
<%= render "zebra_settings" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -21,13 +21,13 @@
|
|||
<div class="flex items-center uppercase text-bold">
|
||||
<% if can_read_experiment?(@my_module.experiment) %>
|
||||
<a class="p-4 border-b-4 border-transparent text-sn-grey hover:no-underline <%= "border-sn-blue" if is_module_protocols? %>"
|
||||
href="<%= protocols_my_module_url(@my_module) %>"
|
||||
href="<%= protocols_my_module_url(@my_module, view_mode: params[:view_mode]) %>"
|
||||
title="<%= t("nav2.modules.steps") %>"
|
||||
>
|
||||
<%=t "nav2.modules.steps" %>
|
||||
</a>
|
||||
<a class="p-4 border-b-4 border-transparent text-sn-grey hover:no-underline <%= "border-sn-blue" if is_module_results? %>"
|
||||
href="<%= results_my_module_url(@my_module) %>"
|
||||
href="<%= results_my_module_url(@my_module, view_mode: params[:view_mode]) %>"
|
||||
title="<%= t("nav2.modules.results") %>"
|
||||
>
|
||||
<%= t("nav2.modules.results") %>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<% if @my_module.completed? %>
|
||||
<div class="flex-block date-block" >
|
||||
<div class="flex-block-label">
|
||||
<span class="fas block-icon fa-calendar-day"></span>
|
||||
<span class="sn-icon sn-icon-calendar mr-2.5"></span>
|
||||
<span class="hidden-xs hidden-sm hidden-md"><%= t('my_modules.details.completed_date') %></span>
|
||||
</div>
|
||||
<div class="datetime-container">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= form_with url: update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row),
|
||||
method: :post,
|
||||
<%= form_with url: update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row),
|
||||
method: :post,
|
||||
html: { data: { remote: true } } do |f| %>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>">
|
||||
|
@ -42,7 +42,7 @@
|
|||
<span class="units"><%= @stock_value.repository_stock_unit_item&.data %></span>
|
||||
</div>
|
||||
<div class="stock-arrow">
|
||||
<i class="sn-icon sn-icon-move"></i>
|
||||
<i class="sn-icon sn-icon-arrow-right"></i>
|
||||
</div>
|
||||
<div class="stock-final-container ">
|
||||
<span class="subtitle"><%= t('repository_stock_values.manage_modal.new_stock') %></span>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
<div class="report-element-body">
|
||||
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8 %>" />
|
||||
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata %>" />
|
||||
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json %>" />
|
||||
<div class="hot-table-container"></div>
|
||||
<table class="report-common-table-format"></table>
|
||||
</div>
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
</span>
|
||||
<div class="move-buttons sci-btn-group">
|
||||
<button class="btn btn-light icon-btn move-up">
|
||||
<i class="sn-icon sn-icon-sort-up"></i>
|
||||
<i class="sn-icon sn-icon-arrow-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-light icon-btn move-down">
|
||||
<i class="sn-icon sn-icon-sort-down"></i>
|
||||
<i class="sn-icon sn-icon-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
<h4 class="modal-title"><%= t('repositories.index.modal_export.title') %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="description-p1" data-team-name="<%= team_name %>">
|
||||
<p class="description-p1">
|
||||
<%= t('repositories.index.modal_export.description_p1_html', team_name: team_name, count: counter) %>
|
||||
</p>
|
||||
<p class="bg-sn-super-light-blue p-3"><%= t('repositories.index.modal_export.description_alert') %></p>
|
||||
<p class="mt-3"><%= t('repositories.index.modal_export.description_p2') %></p>
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<%= "data-items-url=#{items_repository_repository_columns_list_column_path(repository, column)}" if column.repository_list_value? %>
|
||||
<%= "data-items-url=#{items_repository_repository_columns_status_column_path(repository, column)}" if column.repository_status_value? %>
|
||||
>
|
||||
<%= display_tooltip(column.name) %>
|
||||
<div class="truncate"><%= display_tooltip(column.name) %></div>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
|
|
|
@ -1,70 +1,9 @@
|
|||
<% if @breadcrumbs_items&.length %>
|
||||
<% shortened = @breadcrumbs_items.length > 4 %>
|
||||
|
||||
<% if shortened %>
|
||||
<% first_breadcrumb_item = @breadcrumbs_items.shift
|
||||
last_breadcrumb_items = @breadcrumbs_items.pop(2) %>
|
||||
<%= link_to(first_breadcrumb_item[:label], first_breadcrumb_item[:url],
|
||||
class: "breadcrumbs-link",
|
||||
title: first_breadcrumb_item[:label]) %>
|
||||
<span class="delimiter">
|
||||
<%= image_tag "icon_small/navigate_next.svg",
|
||||
alt: "navigate next",
|
||||
class: "navigate_next" %>
|
||||
</span>
|
||||
<span class="breadcrumbs-collapsed-container">
|
||||
<span class="show-breadcrumbs" data-toggle="dropdown" title="<%= t('projects.index.breadcrumbs_collapsed') %>">
|
||||
•••
|
||||
<span class="caret pull-right"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu breadcrumbs-dropdown" role="menu">
|
||||
<% @breadcrumbs_items.each do |item| %>
|
||||
<li>
|
||||
<%= link_to(item[:label], item[:url]) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</span>
|
||||
<span class="delimiter">
|
||||
<%= image_tag "icon_small/navigate_next.svg",
|
||||
alt: "navigate next",
|
||||
class: "navigate_next" %>
|
||||
</span>
|
||||
<% item = last_breadcrumb_items.first %>
|
||||
<%= link_to(item[:label], item[:url],
|
||||
class: "breadcrumbs-link",
|
||||
title: item[:label]) %>
|
||||
<span class="delimiter">
|
||||
<%= image_tag "icon_small/navigate_next.svg",
|
||||
alt: "navigate next",
|
||||
class: "navigate_next" %>
|
||||
</span>
|
||||
<% last_item = last_breadcrumb_items.last %>
|
||||
<span class="breadcrumbs-link" title="<%= last_item[:label] %>">
|
||||
<%= last_item[:label] %>
|
||||
</span>
|
||||
<% else %>
|
||||
<% last_item = @breadcrumbs_items.pop %>
|
||||
<% @breadcrumbs_items.each do |item| %>
|
||||
<% if item[:url] %>
|
||||
<%= link_to(item[:label], item[:url],
|
||||
class: "breadcrumbs-link",
|
||||
title: item[:label]) %>
|
||||
<% else %>
|
||||
<%= content_tag(:span, item[:label],
|
||||
class: "breadcrumbs-link plain-text",
|
||||
title: item[:label]) %>
|
||||
<% end %>
|
||||
|
||||
<span class="delimiter">
|
||||
<img src="<%= asset_path "icon_small/navigate_next.svg"%>"
|
||||
alt="navigate next"
|
||||
class="navigate_next">
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<span class="breadcrumbs-link" title="<%= last_item[:label] %>">
|
||||
<%= last_item[:label] %>
|
||||
</span>
|
||||
<% end %>
|
||||
<div id="breadcrumbs" data-behaviour="vue">
|
||||
<breadcrumbs
|
||||
breadcrumbs-items="<%= @breadcrumbs_items.to_json %>"
|
||||
delimiter-url="<%= asset_path "icon_small/navigate_next.svg" %>"
|
||||
/>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_navigation_breadcrumbs' %>
|
||||
<% end %>
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
</div>
|
||||
<div class="items">
|
||||
<% experiment_group[:experiments].each do |experiment| %>
|
||||
<li class="item" data-name="<%= experiment.name %>" data-id="<%= experiment.id.base62_encode %>" data-type="exp">
|
||||
<li class="item" data-name="<%= sanitize_input(experiment.name) %>" data-id="<%= experiment.id.base62_encode %>" data-type="exp">
|
||||
<span class='sa-type'><%= experiment.code %></span>
|
||||
<span class="dot">·</span>
|
||||
<span class="item-text"><%= experiment.name %></span>
|
||||
<span class="item-text"><%= sanitize_input(experiment.name) %></span>
|
||||
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
</div>
|
||||
<div class="items">
|
||||
<% task_group[:tasks].each do |task| %>
|
||||
<li class="item" data-name="<%= task.name %>" data-id="<%= task.id.base62_encode %>" data-type="tsk">
|
||||
<li class="item" data-name="<%= sanitize_input(task.name) %>" data-id="<%= task.id.base62_encode %>" data-type="tsk">
|
||||
<span class='sa-type'><%= task.code %></span>
|
||||
<span class="dot">·</span>
|
||||
<span class="item-text"><%= task.name %></span>
|
||||
<span class="item-text"><%= sanitize_input(task.name) %></span>
|
||||
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<% limit_reached = projects.length == Constants::ATWHO_SEARCH_LIMIT + 1 %>
|
||||
<div class="atwho-scroll-container">
|
||||
<% projects.limit(Constants::ATWHO_SEARCH_LIMIT).each do |project| %>
|
||||
<li class="item" data-name="<%= project.name %>" data-id="<%= project.id.base62_encode %>" data-type="prj">
|
||||
<li class="item" data-name="<%= sanitize_input(project.name) %>" data-id="<%= project.id.base62_encode %>" data-type="prj">
|
||||
<span class='sa-type'><%= project.code %></span>
|
||||
<span class="dot">·</span>
|
||||
<span class="item-text"><%= project.name %></span>
|
||||
<span class="item-text"><%= sanitize_input(project.name) %></span>
|
||||
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<% limit_reached = repository_rows.length == Constants::ATWHO_SEARCH_LIMIT + 1 %>
|
||||
<div class="atwho-scroll-container">
|
||||
<% repository_rows.take(Constants::ATWHO_SEARCH_LIMIT).each do |row| %>
|
||||
<li class="item" data-name="<%= row[:name] %>" data-id="<%= row[:id_encoded] %>" data-type="rep_item">
|
||||
<li class="item" data-name="<%= sanitize_input(row[:name]) %>" data-id="<%= row[:id_encoded] %>" data-type="rep_item">
|
||||
<span class='sa-type'><%= row[:code] %></span>
|
||||
<span class="dot">·</span>
|
||||
<span class="item-text"><%= row[:name] %></span>
|
||||
<span class="item-text"><%= sanitize_input(row[:name]) %></span>
|
||||
<%= render partial: 'shared/smart_annotation/atwho_control_buttons', locals: { row: row, repository: repository } %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
</div>
|
||||
<div class="atwho-scroll-container">
|
||||
<% users.limit(Constants::ATWHO_SEARCH_LIMIT).each do |user| %>
|
||||
<li class="atwho-user" data-full-name="<%= user.full_name %>" data-id="<%= user.id.base62_encode %>" data-type="rep_item">
|
||||
<li class="atwho-user" data-full-name="<%= sanitize_input(user.full_name) %>" data-id="<%= user.id.base62_encode %>" data-type="rep_item">
|
||||
<img src="<%= avatar_path(user, :icon_small) %>" class="avatar" />
|
||||
<div class="user-info">
|
||||
<div class="user-name item-text"><%= user.full_name %></div>
|
||||
<div class="user-name item-text"><%= sanitize_input(user.full_name) %></div>
|
||||
<div class="user-email item-text"><%= user.email %></div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -20,25 +20,23 @@
|
|||
<div class="row printer-settings">
|
||||
<div class="col-xs-12 col-sm-12">
|
||||
<h2 class="addons-subtitle"><%= t('users.settings.account.addons.label_printers') %></h2>
|
||||
<% if LabelPrinter.zebra_print_enabled? %>
|
||||
<div class="printers-container">
|
||||
<div class="printer">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<%= t('users.settings.account.addons.zebra_printer.title') %>
|
||||
</div>
|
||||
<div class="control">
|
||||
<%= t('users.settings.account.addons.printers.enabled') %>
|
||||
<i class="sn-icon sn-icon-check"></i>
|
||||
</div>
|
||||
<div class="printers-container">
|
||||
<div class="printer">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<%= t('users.settings.account.addons.zebra_printer.title') %>
|
||||
</div>
|
||||
<div class="description">
|
||||
<%= t('users.settings.account.addons.zebra_printer.description') %>
|
||||
<div class="control">
|
||||
<%= t('users.settings.account.addons.printers.enabled') %>
|
||||
<i class="sn-icon sn-icon-check"></i>
|
||||
</div>
|
||||
<%= link_to t('users.settings.account.addons.printers.details'), zebra_settings_path(), class: 'printer-details' %>
|
||||
</div>
|
||||
<div class="description">
|
||||
<%= t('users.settings.account.addons.zebra_printer.description') %>
|
||||
</div>
|
||||
<%= link_to t('users.settings.account.addons.printers.details'), zebra_settings_path(), class: 'printer-details' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="printers-container">
|
||||
<div class="printer">
|
||||
<div class="header">
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue