Merge branch 'develop' into features/september-release

This commit is contained in:
Anton 2023-08-21 11:25:27 +02:00
commit 2d64d2fcbf
135 changed files with 515 additions and 1337 deletions

View file

@ -67,7 +67,7 @@ gem 'scenic', '~> 1.4'
gem 'sdoc', '~> 1.0', group: :doc
gem 'silencer' # Silence certain Rails logs
gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save'
gem 'turbolinks', '~> 5.1.1'
gem 'turbolinks', '~> 5.2.0'
gem 'underscore-rails'
gem 'wicked_pdf'

View file

@ -480,7 +480,7 @@ GEM
pry (>= 0.10.4)
psych (3.3.4)
public_suffix (5.0.1)
puma (6.3.0)
puma (6.3.1)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.1)
@ -646,8 +646,8 @@ GEM
tilt (2.2.0)
timecop (0.9.6)
timeout (0.4.0)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
@ -755,7 +755,7 @@ DEPENDENCIES
puma
rack-attack
rack-cors
rails (~> 7.0.5.1)
rails (~> 7.0.5)
rails-controller-testing
rails_12factor
rails_autolink (~> 1.1, >= 1.1.6)
@ -781,7 +781,7 @@ DEPENDENCIES
sprockets-rails
tailwindcss-rails (~> 2.0)
timecop
turbolinks (~> 5.1.1)
turbolinks (~> 5.2.0)
tzinfo-data
uglifier (>= 1.3.0)
underscore-rails

View file

@ -1,5 +1,6 @@
// turbolinks MUST BE THE LAST inclusion
//= require moment
//= require bootstrap-datetimepicker
//= require bootstrap-colorselector
//= require bootstrap-tagsinput.min

View file

@ -1,7 +1,8 @@
/* eslint-disable no-unused-vars */
/*
global Results ActiveStorage animateSpinner Comments ResultAssets FilePreviewModal
TinyMCE getParam applyCreateWopiFileCallback initFormSubmitLinks textValidator
GLOBAL_CONSTANTS
GLOBAL_CONSTANTS ActiveStoragePreviews
*/
(function(global) {
@ -92,8 +93,9 @@
// Handle the error
} else {
let formData = new FormData();
const assetId = form.find('#result_asset_attributes_id').val();
formData.append('result[name]', form.find('#result_name').val());
formData.append('result[asset_attributes][id]', form.find('#result_asset_attributes_id').val());
formData.append('result[asset_attributes][id]', assetId);
formData.append('result[asset_attributes][signed_blob_id]', blob.signed_id);
$.ajax({
@ -102,7 +104,7 @@
data: formData,
success: function(data) {
animateSpinner(null, false);
$('.edit_result').parent().remove();
$('.edit-result-asset').parent().remove();
$(data.html).prependTo('#results').promise().done(() => {
$.each($('#results').find('.result'), function() {
initFormSubmitLinks($(this));
@ -113,13 +115,14 @@
ResultAssets.initNewResultAsset();
expandResult($(this));
});
ActiveStoragePreviews.reloadPreview(`.asset[data-asset-id=${assetId}] .attachment-preview img`);
});
$('#results-toolbar').show();
},
error: function(XHR) {
animateSpinner(null, false);
$('.edit_result').renderFormErrors('result', XHR.responseJSON.errors);
$('.edit-result-asset').renderFormErrors('result', XHR.responseJSON.errors);
},
processData: false,
contentType: false

View file

@ -147,7 +147,6 @@
noRecentText.show();
}
bindSystemNotificationAjax();
SystemNotificationsMarkAsSeen();
}
});
$('#count-system-notifications').hide();

View file

@ -416,12 +416,12 @@ var ProjectsIndex = (function() {
viewContainer.removeClass('no-results no-data');
viewContainer.find('.card, .projects-group, .no-results-container, .no-data-container').remove();
if (viewContainer.find('.list').length) {
viewContainer.append(data.cards_html);
if (viewContainer.hasClass('list')) {
viewContainer.find('.table-header').show();
}
viewContainer.append(data.cards_html);
if (viewContainer.find('.no-results-container').length) {
viewContainer.addClass('no-results');
}

View file

@ -5,7 +5,7 @@ $.fn.dataTable.render.RepositoryAssetValue = function(data) {
if (asset.id) {
return `
<div class="asset-value-cell">
${asset.icon_html}
<i class="sn-icon sn-icon-${asset.icon_html}"></i>
<div>
<a class="file-preview-link"
id="modal_link${asset.id}"

View file

@ -717,7 +717,9 @@ var RepositoryDatatable = (function(global) {
}
customColumns.each((i, column) => {
var columnData = $(column).data('type') === 'RepositoryStockValue' ? 'stock' : String(columns.length);
const className = $(column).data('type') === 'RepositoryChecklistValue' ? 'checklist-column' : '';
columns.push({
className: className,
visible: true,
searchable: true,
data: columnData,

View file

@ -2,6 +2,20 @@
'use strict';
global.ResultAssets = (function() {
// New asset callback
function createResultAssetCallback() {
$('.new-result-assets-buttons')
.on('click', '.save-result', (event) => {
DragNDropResults.processResult(event); // eslint-disable-line no-undef
})
.on('click', '.cancel-new', () => {
DragNDropResults.destroyAll(); // eslint-disable-line no-undef
});
$('#new-result-assets-select').on('change', '#drag-n-drop-assets', function() {
DragNDropResults.init(this.files); // eslint-disable-line no-undef
});
}
// New result asset behaviour
function initNewResultAsset() {
$('#new-result-asset').on('click', function(event) {
@ -24,6 +38,7 @@
Results.initCancelFormButton($form, initNewResultAsset);
Results.toggleResultEditButtons(false);
dragNdropAssetsInit();
createResultAssetCallback();
},
error: function(xhr, status, e) {
$(this).renderFormErrors('result', xhr.responseJSON, true, e);
@ -34,6 +49,13 @@
});
}
// Save asset callback
function saveResultAssetCallback() {
$('.edit-result-assets-buttons').on('click', '.save-result', (event) => {
Results.processResult(event, Results.ResultTypeEnum.FILE); // eslint-disable-line no-undef
});
}
function applyEditResultAssetCallback() {
$('.edit-result-asset').off('ajax:success ajax:error').on('ajax:success', function(e, data) {
var $result = $(this).closest('.result');
@ -55,6 +77,7 @@
Results.toggleResultEditButtons(false);
$('#result_name').focus();
saveResultAssetCallback();
}).on('ajax:error', function(e, xhr, status, error) {
animateSpinner(null, false);
});

View file

@ -1,7 +0,0 @@
/* global Results */
(function() {
$('.edit-result-assets-buttons').on('click', '.save-result', (event) => {
Results.processResult(event, Results.ResultTypeEnum.FILE);
});
}());

View file

@ -1,15 +0,0 @@
/* global DragNDropResults */
(function() {
$('.new-result-assets-buttons')
.on('click', '.save-result', (event) => {
DragNDropResults.processResult(event);
})
.on('click', '.cancel-new', () => {
DragNDropResults.destroyAll();
});
$('#new-result-assets-select').on('change', '#drag-n-drop-assets', function() {
DragNDropResults.init(this.files);
});
}());

View file

@ -64,6 +64,20 @@
});
}
// Save result table callback
function newResultTableCallback() {
$('.new-result-tables-buttons .save-result').on('click', (event) => {
Results.processResult(event, Results.ResultTypeEnum.TABLE);
});
}
// Save result table callback
function saveResultTableCallback() {
$('.edit-result-tables-buttons .save-result').on('click', (event) => {
Results.processResult(event, Results.ResultTypeEnum.TABLE);
});
}
// Apply ajax callback to form
function _formAjaxResultTable($form, $prevResult) {
$form.on('ajax:success', function(e, data) {
@ -111,6 +125,7 @@
Results.toggleResultEditButtons(false);
$('#result_name').focus();
saveResultTableCallback();
});
}
@ -137,6 +152,7 @@
Results.initCancelFormButton($form, initNewResultTable);
Results.toggleResultEditButtons(false);
$('#result_name').focus();
newResultTableCallback();
},
error: function() {
animateSpinner(null, false);

View file

@ -1,7 +0,0 @@
/* global Results */
(function() {
$('.edit-result-tables-buttons').on('click', '.save-result', (event) => {
Results.processResult(event, Results.ResultTypeEnum.TABLE);
});
}());

View file

@ -1,7 +0,0 @@
/* global Results */
(function() {
$('.new-result-tables-buttons').on('click', '.save-result', (event) => {
Results.processResult(event, Results.ResultTypeEnum.TABLE);
});
}());

View file

@ -27,6 +27,12 @@ var ActiveStoragePreviews = (function() {
showPreview: function(ev) {
$(ev.target).css('opacity', 1);
$(ev.target).parent().removeClass('processing');
},
reloadPreview: function(target) {
$(target)
.one('error', (event) => this.reCheckPreview(event))
.one('load', (event) => this.showPreview(event))
.trigger('error');
}
});
}());
@ -37,9 +43,9 @@ $(document).on('turbolinks:load', function() {
.one('error', (event) => ActiveStoragePreviews.reCheckPreview(event))
.each(function() {
if (this.complete) {
$(this).load();
$(this).trigger('load');
} else if (this.error) {
$(this).error();
$(this).trigger('error');
}
});
});

View file

@ -30,7 +30,7 @@ var DataTableHelpers = (function() {
.removeClass('form-control input-sm')
.css('margin', 0);
$('.dataTables_filter').append(`
<button class="btn btn-light btn-lg icon-btn search-icon btn-black"
<button class="btn btn-light icon-btn search-icon btn-black"
title="${I18n.t('repositories.show.button_tooltip.search')}">
<i class="sn-icon sn-icon-search"></i>
</button>

View file

@ -1,5 +1,6 @@
/* global animateSpinner fabric PerfectScrollbar refreshProtocolStatusBar tui Uint8Array ActiveStoragePreviews*/
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-underscore-dangle no-unused-vars */
var ImageEditorModal = (function() {
@ -369,9 +370,13 @@ var ImageEditorModal = (function() {
$(`.asset[data-asset-id=${data.id}] .attachment-preview img`)
.replaceWith($(res.html).find('.attachment-preview img'));
$(`.asset[data-asset-id=${data.id}]`).closest('.attachments').trigger('reorder');
<<<<<<< HEAD
$(`.asset[data-asset-id=${data.id}] .attachment-preview img`)
.one('error', (event) => ActiveStoragePreviews.reCheckPreview(event))
.one('load', (event) => ActiveStoragePreviews.showPreview(event));
=======
ActiveStoragePreviews.reloadPreview(`.asset[data-asset-id=${data.id}] .attachment-preview img`);
>>>>>>> develop
closeEditor();
}
});

View file

@ -5,7 +5,6 @@ function initShowPassword() {
style="
cursor: pointer;
z-index: 10;
top: ${$(e).position().top}px
"></i>`).insertAfter(e);
$(e).parent().addClass('right-icon');
});

View file

@ -1,45 +0,0 @@
'use strict';
// update selected notiifcations
function SystemNotificationsMarkAsSeen() {
if ($('.system-notification[data-new="1"]').length > 0) {
$.post('/system_notifications/mark_as_seen');
}
}
function bindSystemNotificationAjax() {
var SystemNotificationModal = null;
var SystemNotificationModalBody = null;
var SystemNotificationModalTitle = null;
SystemNotificationModal = $('#manage-module-system-notification-modal');
SystemNotificationModalBody = SystemNotificationModal.find('.modal-body');
SystemNotificationModalTitle = SystemNotificationModal.find('#manage-module-system-notification-modal-label');
$('.modal-system-notification')
.on('ajax:success', function(ev, data) {
var SystemNotification = $('.system-notification[data-system-notification-id=' + data.id + ']');
SystemNotificationModalBody.html(data.modal_body);
SystemNotificationModalTitle.text(data.modal_title);
$('.dropdown.system-notifications').removeClass('open');
// Open modal
SystemNotificationModal.modal('show');
});
}
function initSystemNotificationsButton() {
$('.btn-more-system-notifications')
.on('ajax:success', function(e, data) {
$(data.html).insertAfter($('.system-notifications-container .system-notification').last());
bindSystemNotificationAjax();
if (data.more_url) {
$(this).attr('href', data.more_url);
} else {
$(this).remove();
}
});
}
initSystemNotificationsButton();
SystemNotificationsMarkAsSeen();
bindSystemNotificationAjax();

View file

@ -1 +0,0 @@
$('#manage-module-system-notification-modal').modal('show');

View file

@ -125,6 +125,7 @@
@import "projects";
@import "protocol_management";
@import "report_index";
@import "reports";
@import "reports_pdf.sass.scss";
@import "reports_print";
@import "repositories";

View file

@ -33,6 +33,12 @@
.create-new {
padding: 0 5px;
}
.dropdown-option {
overflow: hidden;
text-overflow: clip;
white-space: nowrap;
}
}
.new-projects-visibility {

View file

@ -17,11 +17,8 @@
.search-container {
flex-basis: 36px;
.fa-search {
animation-timing-function: $timing-function-sharp;
color: $color-alto;
transition: .3s;
width: 26px;
.sn-icon {
position: absolute;
}
.task-search-field {
@ -36,10 +33,6 @@
border: $border-transparent;
cursor: pointer;
width: 36px;
+ .fa-search {
color: $color-volcano;
}
}
&:hover {
@ -50,10 +43,6 @@
border: $border-focus;
cursor: auto;
width: 200px;
+ .fa-search {
color: $color-alto;
}
}

View file

@ -90,7 +90,7 @@
}
}
@media (max-width: 1100px) {
@media (max-width: 1240px) {
.dashboard-container {
--dashboard-widgets-gap: 15px;
grid-template-columns: 100%;

View file

@ -149,7 +149,7 @@
.experiment-table {
display: grid;
grid-auto-rows: 3em 1px;
grid-template-columns: max-content repeat(calc(var(--columns-count)), minmax(max-content, auto)) max-content;
grid-template-columns: max-content repeat(calc(var(--columns-count)), minmax(100px, auto)) max-content;
min-width: 100%;
.table-header-cell {
@ -518,7 +518,6 @@
.task_name-column {
a {
max-width: 320px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;

View file

@ -60,7 +60,7 @@
.label-templates-datatable {
--content-header-size: 3.5rem;
height: calc(100vh - var(--navbar-height) - var(--content-header-size));
height: calc(100vh - var(--top-navigation-height) - var(--breadcrumbs-navigation-height) - var(--content-header-size));
#label-templates-table_wrapper {
display: flex;

View file

@ -613,12 +613,17 @@
}
.status-label {
@include font-small;
@include font-button;
flex-basis: 50%;
font-weight: 700;
margin-right: .5em;
text-align: right;
.status-title {
@include font-small;
color: var(--sn-grey);
margin-right: .2em;
}
.status-note {
font-weight: normal;
}

View file

@ -8,9 +8,15 @@
.dropdown-toggle {
border: 0;
color: $color-white;
padding: .5rem .5rem .5rem 1rem;
text-align: left;
width: 100%;
&.btn-not-started {
border: 1px solid var(--sn-sleepy-grey);
color: var(--sn-black);
}
.caret {
margin: 8px 0;
}
@ -67,6 +73,11 @@
white-space: nowrap;
}
.btn-not-started {
border: 1px solid var(--sn-sleepy-grey);
color: var(--sn-black);
}
.error-message {
@include font-small;
color: $color-silver-chalice;
@ -126,6 +137,11 @@
line-height: 1em;
padding: .5em;
white-space: nowrap;
&.btn-not-started {
border: 1px solid var(--sn-sleepy-grey);
color: var(--sn-black);
}
}
.status-comment {

View file

@ -58,24 +58,6 @@
}
}
.sci--navigation--notificaitons-flyout-tabs {
align-items: center;
display: flex;
flex-basis: 2.5rem;
flex-shrink: 0;
gap: 3rem;
}
.sci--navigation--notificaitons-flyout-tab {
cursor: pointer;
padding: .5rem .625rem;
position: relative;
&.active {
color: $brand-focus;
}
}
hr {
margin: .625rem 0;
}

View file

@ -114,4 +114,10 @@
}
}
}
@-moz-document url-prefix() {
input {
padding: 0 4px;
}
}
}

View file

@ -964,9 +964,8 @@ li.module-hover {
.name {
align-items: center;
grid-column: 1 span;
-webkit-box-orient: vertical;
display: -webkit-box;
display: flex;
-webkit-line-clamp: 2;
}

View file

@ -49,8 +49,7 @@
padding: .1em 0;
&:hover {
border: 1px solid $color-silver;
border-radius: 3px;
border: 1px solid transparent;
}
}
}

View file

@ -89,7 +89,7 @@
}
}
td:nth-child(4) {
min-width: min-content;
min-width: 60px;
}
}

View file

@ -42,24 +42,20 @@ div.print-report {
.report-project-header-element {
& > .report-element-body .project-name {
color: $color-black;
white-space: nowrap;
}
&:hover > .report-element-body .project-name {
color: $color-black;
white-space: nowrap;
}
}
.report-module-element:hover {
& > .report-element-body .module-name {
color: $color-black;
white-space: nowrap;
}
&:hover > .report-element-body .module-name {
color: $color-black;
white-space: nowrap;
}
}

View file

@ -81,17 +81,20 @@
}
// Cells
td {
white-space: nowrap;
td.added-on,
td.asset-value-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Assigned
.assigned-column {
overflow: unset;
padding: 1px 8px;
position: relative;
white-space: nowrap;
.sn-icon.sn-icon-edit {
line-height: 35px;
@ -170,6 +173,10 @@
}
}
.checklist-column {
overflow: unset;
}
.item-name {
padding: 2px 8px;
@ -414,6 +421,10 @@
}
}
}
td {
overflow: visible;
}
}
}
}

View file

@ -178,6 +178,7 @@
}
.vis {
display: inline-block;
margin-left: 10px;
margin-right: 5px;

View file

@ -29,6 +29,10 @@
}
}
.team-share-permission:disabled {
cursor: not-allowed;
}
.team-description {
display: inline-block;
margin-top: 16px;
@ -90,16 +94,7 @@
}
.button-container {
position: absolute;
right: 0;
top: -20px;
.inline-field-button {
color: $color-silver-chalice;
cursor: pointer;
line-height: 20px;
padding-left: 20px;
}
top: -32px;
}
&:hover .input-field,

View file

@ -9,6 +9,10 @@
font-size: 13px;
}
.datepicker thead th {
padding: 5px 5px !important;
}
.timepicker-picker {
padding: 0 60px;

View file

@ -50,6 +50,7 @@
}
.sn-icon {
bottom: 6px;
position: absolute;
text-align: center;
width: 25px;
@ -79,10 +80,11 @@
&.error {
&::after {
@include font-awesome;
top: 0;
bottom: 6px;
position: absolute;
right: 5px;
text-align: center;
top: 6px;
width: 25px;
}
}

View file

@ -52,6 +52,12 @@ input[type="checkbox"].sci-toggle-checkbox {
}
}
&:focus + .sci-toggle-checkbox-label {
box-shadow: 0 0 0 4px var(--sn-science-blue-hover);
outline: 2px solid transparent;
outline-offset: 2px;
}
&.hidden + .sci-toggle-checkbox-label {
display: none;
}

View file

@ -0,0 +1,167 @@
// scss-lint:disable SelectorDepth
// scss-lint:disable NestingDepth
:root {
--attachment-column-width: 16em;
--attachment-row-height: 3em;
}
.step-container {
.step-attachments {
padding-left: 28px;
}
.attachments-actions {
align-items: center;
display: flex;
flex-wrap: wrap;
.title {
flex-grow: 1;
flex-shrink: 0;
}
.dropdown-attachment-options {
@include font-button;
min-width: 200px;
.divider-label {
@include font-small;
color: $color-silver-chalice;
padding-left: 1em;
pointer-events: none;
}
a {
border-radius: unset;
cursor: pointer;
padding: .5em 1em;
&:hover {
background: $color-concrete;
}
}
.action-link {
&.selected::after {
@include font-awesome;
content: $font-fas-check;
margin-left: auto;
position: absolute;
right: 1em;
}
}
.change-order {
padding-left: 2.75em;
}
.attachments-view-mode {
.fas {
width: 1.5em;
}
}
}
}
.attachments {
display: grid;
grid-auto-rows: var(--attachment-row-height);
grid-column-gap: 1em;
grid-row-gap: 1em;
grid-template-columns: repeat(auto-fill, minmax(var(--attachment-column-width), 1fr));
margin: 1em 0;
.asset-context-menu {
.marvinjs-edit-button img {
margin-left: -.6em;
margin-right: 0;
width: 2em;
}
}
&[data-preview="true"] {
.asset-context-menu {
display: none;
}
}
}
.add-file-modal {
.file-drop-zone {
align-items: center;
background: $color-concrete;
display: flex;
flex-direction: column;
justify-content: center;
padding: 2em;
.btn.title {
cursor: pointer;
font-size: 1em;
&:hover {
background-color: $color-alto;
}
.fas {
margin: 0 .5em;
}
}
.description {
color: $color-silver-chalice;
margin-top: 1.5em;
}
}
.drop-notification {
display: none;
}
&.draging-file {
.file-drop-zone {
background-color: $brand-focus-light;
position: relative;
.drop-notification {
@include font-h3;
align-items: center;
color: $brand-primary;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
}
.title,
.description,
.available-storage {
opacity: 0;
}
* {
pointer-events: none;
}
}
}
.divider {
@include font-small;
font-weight: bold;
padding: 1em 0;
text-align: center;
}
.integrations-container {
align-items: center;
display: flex;
justify-content: center;
.integration-block {
margin: 0 1em;
}
}
}
}

View file

@ -83,9 +83,9 @@
}
.btn.btn-shared {
background-color: var(--sn-white);
border: 1px solid var(--sn-coral);
color: var(--sn-coral);
background-color: var(--sn-science-blue);
border: 1px solid var(--sn-white);
color: var(--sn-white);
}
.btn.btn-secondary:hover,

View file

@ -1,6 +1,6 @@
@layer components {
.sci-input-container-v2 {
@apply relative h-[2.75rem] flex items-center;
@apply relative h-[2.5rem] flex items-center;
}
.sci-input-container-v2.input-sm {
@ -25,7 +25,7 @@
}
.sci-input-container-v2 input:focus {
border-color: var(--sn-sleepy-grey);
border-color: var(--sn-science-blue);
box-shadow: none;
}

View file

@ -174,7 +174,7 @@ class LabelTemplatesController < ApplicationController
end
def load_label_templates
@label_templates = LabelTemplate.where(team_id: current_team.id)
@label_templates = LabelTemplate.enabled? ? current_team.label_templates : current_team.label_templates.default
end
def load_label_template

View file

@ -323,12 +323,13 @@ class ReportsController < ApplicationController
def load_wizard_vars
@templates = Extends::REPORT_TEMPLATES
live_repositories = Repository.accessible_by_teams(current_team).active
live_repositories = Repository.accessible_by_teams(current_team).sort_by { |r| r.name.downcase }
snapshots_of_deleted = RepositorySnapshot.left_outer_joins(:original_repository)
.where(team: current_team)
.where.not(original_repository: live_repositories)
.select('DISTINCT ON ("repositories"."parent_id") "repositories".*')
@repositories = (live_repositories + snapshots_of_deleted).sort_by { |r| r.name.downcase }
.sort_by { |r| r.name.downcase }
@repositories = live_repositories + snapshots_of_deleted
@visible_projects = current_team.projects
.active
.joins(experiments: :my_modules)

View file

@ -1,56 +0,0 @@
# frozen_string_literal: true
class SystemNotificationsController < ApplicationController
def show
current_user.user_system_notifications.mark_as_read(params[:id])
render json: current_user.system_notifications.modals
.find_by_id(params[:id]) || {}
end
# Update seen_at parameter for system notifications
def mark_as_seen
current_user.user_system_notifications.mark_as_seen
render json: { result: 'ok' }
rescue StandardError
render json: { result: 'failed' }
end
# Update read_at parameter for system notifications
def mark_as_read
current_user.user_system_notifications.mark_as_read(params[:id])
render json: { result: 'ok' }
rescue StandardError
render json: { result: 'failed' }
end
def unseen_counter
render json: {
notificationNmber: current_user.user_system_notifications.unseen.count
}
end
private
def prepare_notifications
page = (params[:page] || 1).to_i
query = params[:search_queue]
per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT
notifications = SystemNotification.last_notifications(current_user, query)
.page(page)
.per(per_page)
unless notifications.blank? || notifications.last_page?
more_url = url_for(
system_notifications_url(
format: :json,
page: page + 1,
search_queue: query
)
)
end
@system_notifications = {
notifications: notifications,
more_notifications_url: more_url
}
end
end

View file

@ -13,44 +13,20 @@ class UserNotificationsController < ApplicationController
UserNotification.where(
notification_id: notifications.except(:select).where.not(type_of: 2).select(:id)
).seen_by_user(current_user)
current_user.user_system_notifications.where(
system_notification_id: notifications.except(:select).where(type_of: 2).select(:id)
).mark_as_seen
end
def unseen_counter
render json: {
unseen: load_notifications.where(checked: false).size
unseen: load_notifications.where('user_notifications.checked = ?', false).size
}
end
private
def load_notifications
user_notifications = current_user.notifications
.select(:id, :type_of, :title, :message, :created_at, 'user_notifications.checked')
system_notifications = current_user.system_notifications
.select(
:id,
'2 AS type_of',
:title,
'description AS message',
:created_at,
'CASE WHEN seen_at IS NULL THEN false ELSE true END AS checked'
)
notifications =
case params[:type]
when 'message'
user_notifications
when 'system'
Notification.from("(#{system_notifications.to_sql}) AS notifications")
else
Notification.from(
"((#{user_notifications.to_sql}) UNION ALL (#{system_notifications.to_sql})) AS notifications"
)
end
notifications.order(created_at: :desc)
current_user.notifications
.select(:id, :type_of, :title, :message, :created_at, 'user_notifications.checked')
.order(created_at: :desc)
end
def notification_serializer(notifications)
@ -62,8 +38,7 @@ class UserNotificationsController < ApplicationController
message: notification.message,
created_at: I18n.l(notification.created_at, format: :full),
today: notification.created_at.today?,
checked: notification.checked,
action_url: (system_notification_path(notification.id) if notification.type_of == 'system_message')
checked: notification.checked
}
end
end

View file

@ -2,7 +2,6 @@
class Users::SessionsController < Devise::SessionsController
layout :session_layout
after_action :after_sign_in, only: %i(create authenticate_with_two_factor)
before_action :remove_authenticate_mesasge_if_root_path, only: :new
prepend_before_action :skip_timeout, only: :expire_in
@ -58,10 +57,6 @@ class Users::SessionsController < Devise::SessionsController
@initial_page = stored_location_for(:user)
end
def after_sign_in
flash[:system_notification_modal] = true
end
def authenticate_with_two_factor
user = User.find_by(id: session[:otp_user_id])

View file

@ -66,7 +66,7 @@ class TeamUsersDatatable < CustomDatatable
'2': record.user_role.name,
'3': I18n.l(record.created_at, format: :full_date),
'4': record.user.active_status_str,
'5': ApplicationController.new.render_to_string(
'5': @view.controller.render_to_string(
partial: 'users/settings/teams/user_dropdown',
locals: {
user_assignment: record,

View file

@ -41,7 +41,7 @@ module SecondaryNavigationHelper
end
def is_module_results?
action_name == 'results'
%w(results results_show).include?(action_name)
end
def is_module_activities?

View file

@ -10,7 +10,6 @@ require('jquery-ui/ui/effects/effect-slide');
require('hammerjs');
import 'bootstrap';
import './bootstrap.less';
window.moment = require('moment');
require('bootstrap-select/js/bootstrap-select');
window.bwipjs = require('bwip-js');
@ -38,3 +37,13 @@ $(document).on('click', '.btn', function() {
$(this).blur();
});
// Needed to support Turbolinks redirect_to responses as unsafe-inline is blocked by the CSP
$.ajaxSetup({
converters: {
'text script': function(text) {
$.globalEval(text, { nonce: document.querySelector('meta[name="csp-nonce"]').getAttribute('content') });
return text;
}
}
});

View file

@ -108,6 +108,7 @@
// Components
@import "~bootstrap/less/component-animations.less";
@import "~bootstrap/less/dropdowns.less";
@import "~bootstrap/less/button-groups.less";
@import "~bootstrap/less/input-groups.less";
@import "~bootstrap/less/navs.less";
@import "~bootstrap/less/navbar.less";

View file

@ -9,8 +9,7 @@
<div class="sci-navigation--notificaitons-flyout-notification-title"
v-html="notification.title"
:data-seen="notification.checked"></div>
<div v-if="notification.type_of !== 'system_message'" v-html="notification.message" class="sci-navigation--notificaitons-flyout-notification-message"></div>
<a v-else @click="showSystemNotification()" class="sci-navigation--notificaitons-flyout-notification-message" data-notification="system">{{ i18n.t('nav.notifications.read_more') }}</a>
<div v-html="notification.message" class="sci-navigation--notificaitons-flyout-notification-message"></div>
</div>
</template>
@ -25,26 +24,12 @@ export default {
switch(this.notification.type_of) {
case 'deliver':
return 'fa-truck';
case 'system_message':
return 'fa-gift';
case 'assignment':
return 'fa-list-alt';
case 'recent_changes':
return 'fa-list-alt';
}
}
},
methods: {
showSystemNotification() {
$.get(this.notification.action_url, (data) => {
let systemNotificationModal = $('#manage-module-system-notification-modal');
let systemNotificationModalBody = systemNotificationModal.find('.modal-body');
let systemNotificationModalTitle = systemNotificationModal.find('#manage-module-system-notification-modal-label');
systemNotificationModalBody.html(data.modal_body);
systemNotificationModalTitle.text(data.modal_title);
systemNotificationModal.modal('show');
});
}
}
}
</script>

View file

@ -4,24 +4,6 @@
{{ i18n.t('nav.notifications.title') }}
<i class="sn-icon sn-icon-close" @click="$emit('close')"></i>
</div>
<div class="sci--navigation--notificaitons-flyout-tabs">
<div class="sci--navigation--notificaitons-flyout-tab"
:data-unseen="unseenNotificationsCount"
@click="setActiveTab('all')"
:class="{'active': activeTab == 'all', 'has-unseen': unseenNotificationsCount > 0}">
{{ i18n.t('nav.notifications.all') }}
</div>
<div class="sci--navigation--notificaitons-flyout-tab"
@click="setActiveTab('message')"
:class="{'active': activeTab == 'message'}">
{{ i18n.t('nav.notifications.message') }}
</div>
<div class="sci--navigation--notificaitons-flyout-tab"
@click="setActiveTab('system')"
:class="{'active': activeTab == 'system'}">
{{ i18n.t('nav.notifications.system') }}
</div>
</div>
<hr>
<perfect-scrollbar ref="scrollContainer" class="sci--navigation--notificaitons-flyout-notifications">
<div class="sci-navigation--notificaitons-flyout-subtitle" v-if="todayNotifications.length" >
@ -55,7 +37,6 @@ export default {
data() {
return {
notifications: [],
activeTab: 'all',
nextPage: 1,
scrollBar: null,
loadingPage: false
@ -66,8 +47,10 @@ export default {
},
mounted() {
let container = this.$refs.scrollContainer.$el
container.addEventListener('ps-y-reach-end', (e) => {
this.loadNotifications();
container.addEventListener('ps-scroll-y', (e) => {
if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - 20) {
this.loadNotifications();
}
})
},
computed: {
@ -82,17 +65,11 @@ export default {
}
},
methods: {
setActiveTab(selection) {
this.activeTab = selection;
this.nextPage = 1;
this.notifications = [];
this.loadNotifications();
},
loadNotifications() {
if (this.nextPage == null || this.loadingPage) return;
this.loadingPage = true;
$.getJSON(this.notificationsUrl, { type: this.activeTab, page: this.nextPage }, (result) => {
$.getJSON(this.notificationsUrl, { page: this.nextPage }, (result) => {
this.notifications = this.notifications.concat(result.notifications);
this.nextPage = result.next_page;
this.loadingPage = false;

View file

@ -83,7 +83,7 @@
</div>
</div>
<div id="protocol-description-container" :class=" inRepository ? 'protocol-description collapse in' : ''" >
<div v-if="urls.update_protocol_description_url">
<div class="ml-1" v-if="urls.update_protocol_description_url">
<Tinymce
:value="protocol.attributes.description"
:value_html="protocol.attributes.description_view"

View file

@ -33,7 +33,7 @@
</p>
<p class="data-block" v-if="protocol.attributes.published">
<span>{{ i18n.t("protocols.header.published_by") }}</span>
<img :src="protocol.attributes.published_by.avatar"/>
<img :src="protocol.attributes.published_by.avatar" class="rounded-full"/>
{{ protocol.attributes.published_by.name }}
</p>
<p class="data-block">
@ -46,7 +46,7 @@
</p>
<p class="data-block">
<span>{{ i18n.t("protocols.header.added_by") }}</span>
<img :src="protocol.attributes.added_by.avatar"/>
<img :src="protocol.attributes.added_by.avatar" class="rounded-full"/>
{{ protocol.attributes.added_by.name }}
</p>
<p class="data-block authors-data">

View file

@ -3,7 +3,7 @@
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">
<button :class="{hidden: searchOpened}" ref='searchInputBtn' class="btn btn-light 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">
@ -28,7 +28,7 @@
<i class='sn-icon sn-icon-barcode barcode-scanner !mr-2.5'></i>
</div>
</div>
<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">
<button :class="{hidden: barcodeSearchOpened}" ref='barcodeSearchInputBtn' class="btn btn-light 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>

View file

@ -12,8 +12,8 @@
<p>{{ i18n.t('shareable_links.destroy_modal.description')}}</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-danger" @click="confirm">{{ i18n.t('shareable_links.destroy_modal.deactivate')}}</button>
<button class="btn btn-secondary" tabindex="0" @click="cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-danger" tabindex="0" @click="confirm">{{ i18n.t('shareable_links.destroy_modal.deactivate')}}</button>
</div>
</div>
</div>

View file

@ -18,6 +18,7 @@
type="button"
class="close float-right !ml-auto"
data-dismiss="modal"
tabindex="0"
aria-label="Close"
>
<i class="sn-icon sn-icon-close"></i>
@ -31,6 +32,7 @@
v-model="sharedEnabled"
id="checkbox"
class="sci-toggle-checkbox"
tabindex="0"
@change="checkboxChange"
@keyup.enter="handleCheckboxEnter"/>
<span class="sci-toggle-checkbox-label"></span>
@ -39,6 +41,7 @@
<div>
<div class="sci-input-container-v2 textarea-lg mb-2">
<textarea ref="textarea"
tabindex="0"
class="sci-input-field"
:class="{ 'error': error }"
v-model="description"
@ -54,8 +57,8 @@
<div class="mb-2" v-if="editing">
<div class="sci-btn-group flex justify-end">
<button class="btn btn-secondary btn-sm" tabindex="-1" @mousedown="cancelDescriptionEdit">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-secondary btn-sm" tabindex="-1" @mousedown="saveDescription" :disabled="error">{{ i18n.t('general.save') }}</button>
<button class="btn btn-secondary btn-sm" tabindex="0" @mousedown="cancelDescriptionEdit">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-secondary btn-sm" tabindex="0" @mousedown="saveDescription" :disabled="error">{{ i18n.t('general.save') }}</button>
</div>
</div>
</div>
@ -71,6 +74,7 @@
:disabled="true"
/>
<button class="btn btn-primary share-link-copy"
tabindex="0"
@click="copy($refs.clone.value)"
:disabled="!sharedEnabled">{{ i18n.t('shareable_links.modal.copy_button') }}
</button>
@ -159,8 +163,11 @@
$(this.$refs.modal).modal('hide');
},
copy(value) {
navigator.clipboard.writeText(value);
HelperModule.flashAlertMsg(this.i18n.t('shareable_links.modal.copy_success'), 'success');
navigator.clipboard.writeText(value).then(
() => {
HelperModule.flashAlertMsg(this.i18n.t('shareable_links.modal.copy_success'), 'success');
}
);
},
saveDescription() {
this.dirty = true;

View file

@ -39,7 +39,7 @@
},
disabled: {
type: Boolean,
defaul: true
default: false
}
},
data() {

View file

@ -88,8 +88,8 @@ module Protocols
def create_step_table_element!(step, step_element_json)
table = Table.create!(
name: step_element_json['name'],
contents: step_element_json['table']['contents'],
name: step_element_json['name'].presence || 'New table',
contents: step_element_json['contents'],
created_by: @user,
last_modified_by: @user,
team: @team
@ -100,13 +100,13 @@ module Protocols
def create_step_list_element!(step, step_element_json)
checklist = Checklist.create!(
name: step_element_json['list']['name'].presence || 'New list',
name: step_element_json['name'].presence || 'New list',
step: step,
created_by: @user,
last_modified_by: @user
)
step_element_json['list']['contents'].each do |item|
step_element_json['contents'].each do |item|
checklist.checklist_items.create!(
text: item.truncate(Constants::TEXT_MAX_LENGTH),
checked: false,

View file

@ -22,16 +22,4 @@ class AppMailer < Devise::Mailer
}.merge(opts)
mail(headers)
end
def system_notification(user, system_notification, opts = {})
@user = user
@system_notification = system_notification
headers = {
to: @user.email,
subject: t('system_notifications.emails.subject')
}.merge(opts)
mail(headers)
end
end

View file

@ -15,6 +15,8 @@ class LabelTemplate < ApplicationRecord
validate :ensure_single_default_template!
scope :default, -> { where(default: true) }
def self.enabled?
ApplicationSettings.instance.values['label_templates_enabled'] == true
end

View file

@ -1,51 +0,0 @@
# frozen_string_literal: true
class SystemNotification < ApplicationRecord
include PgSearch::Model
scope :modals, -> { select(:modal_title, :modal_body, :id) }
# Full text postgreSQL search configuration
pg_search_scope :search_notifications, against: %i(title description),
using: {
tsearch: {
dictionary: 'english'
}
}
# ignoring: :accents
has_many :user_system_notifications, dependent: :destroy
has_many :users, through: :user_system_notifications
validates :title, :modal_title, :modal_body, :description, :source_created_at, :source_id, :last_time_changed_at,
presence: true
validates :title, :description, :modal_title, length: { maximum: Constants::NAME_MAX_LENGTH }
validates :modal_body, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
def self.last_notifications(
user,
query = nil
)
notifications = order(created_at: :DESC)
notifications = notifications.search_notifications(query) if query.present?
notifications.joins(:user_system_notifications)
.where('user_system_notifications.user_id = ?', user.id)
.select(
'system_notifications.id',
:title,
:description,
:last_time_changed_at,
:created_at,
'user_system_notifications.seen_at',
'user_system_notifications.read_at'
)
end
def self.last_sync_timestamp
# If no notifications are present, the created_at of the
# first user is used as the "initial sync time-point"
SystemNotification
.order(last_time_changed_at: :desc)
.first&.last_time_changed_at&.to_i ||
User.order(created_at: :asc).first&.created_at&.to_i
end
end

View file

@ -307,8 +307,6 @@ class User < ApplicationRecord
has_many :user_notifications, inverse_of: :user
has_many :notifications, through: :user_notifications
has_many :user_system_notifications, dependent: :destroy
has_many :system_notifications, through: :user_system_notifications
has_many :zip_exports, inverse_of: :user, dependent: :destroy
has_many :view_states, dependent: :destroy
@ -514,12 +512,6 @@ class User < ApplicationRecord
user_identities.exists?(provider: provider)
end
# This method must be overwriten for addons that will be installed
def show_login_system_notification?
user_system_notifications.show_on_login.present? &&
(ENV['ENABLE_TUTORIAL'] != 'true' || settings['tutorial_completed'])
end
# json friendly attributes
NOTIFICATIONS_TYPES = %w(assignments_notification recent_notification
assignments_email_notification

View file

@ -1,45 +0,0 @@
# frozen_string_literal: true
class UserSystemNotification < ApplicationRecord
belongs_to :user
belongs_to :system_notification
validates :system_notification, uniqueness: { scope: :user }
scope :unseen, -> { where(seen_at: nil) }
def self.mark_as_seen
unseen.update_all(seen_at: Time.now)
end
def self.mark_as_read(notification_id)
notification = find_by_system_notification_id(notification_id)
notification.update(read_at: Time.now) if notification && notification.read_at.nil?
end
def self.show_on_login(update_read_time = false)
# for notification check leave update_read_time empty
notification = joins(:system_notification)
.where('system_notifications.show_on_login = true')
.order('system_notifications.created_at DESC')
.select(
:modal_title,
:modal_body,
'user_system_notifications.id',
:read_at,
:user_id,
:system_notification_id,
:created_at
)
.first
if notification && notification.read_at.nil?
if update_read_time
notification.update(
read_at: Time.now,
seen_at: Time.now
)
end
notification
end
end
end

View file

@ -12,7 +12,7 @@ module RepositoryDatatable
url: rails_blob_path(asset.file, disposition: 'attachment'),
preview_url: asset_file_preview_path(asset),
file_name: escape_input(asset.file_name),
icon_html: FileIconsHelper.file_extension_icon_html(asset)
icon_html: FileIconsHelper.sn_icon_for(asset)
}
rescue StandardError => e
Rails.logger.error e.message

View file

@ -1,27 +0,0 @@
# frozen_string_literal: true
module Notifications
class HandleSystemNotificationInCommunicationChannelService
extend Service
attr_reader :errors
def initialize(system_notification)
@system_notification = system_notification
@errors = {}
end
def call
@system_notification.user_system_notifications.find_each do |usn|
user = usn.user
AppMailer.delay.system_notification(user, @system_notification) if user.system_message_email_notification
end
self
end
def succeed?
@errors.none?
end
end
end

View file

@ -1,40 +0,0 @@
# frozen_string_literal: true
module Notifications
class PushToCommunicationChannelService
extend Service
WHITELISTED_ITEM_TYPES = %w(SystemNotification).freeze
attr_reader :errors
def initialize(item_id:, item_type:)
@item_type = item_type
@item = item_type.constantize.find item_id
@errors = {}
end
def call
return self unless valid?
"Notifications::Handle#{@item_type}InCommunicationChannelService".constantize.call(@item)
self
end
def succeed?
@errors.none?
end
private
def valid?
raise 'Dont know how to handle this type of items' unless WHITELISTED_ITEM_TYPES.include?(@item_type)
if @item.nil?
@errors[:invalid_arguments] = 'Can\'t find item' if @item.nil?
return false
end
true
end
end
end

View file

@ -1,117 +0,0 @@
# frozen_string_literal: true
module Notifications
class SyncSystemNotificationsService
extend Service
include HTTParty
base_uri Rails.application.secrets.system_notifications_uri
SYNC_TIMESTAMP_CACHE_KEY = 'system_notifications_last_sync_timestamp'
attr_reader :errors
def initialize
@errors = {}
end
def call
call_api
save_new_notifications if succeed?
self
end
def succeed?
@errors.none?
end
def self.available?
channel = Rails.application.secrets.system_notifications_channel
query = { query: { last_sync_timestamp: Time.now.to_i, channels_slug: channel },
headers: { 'accept': 'application/vnd.system-notifications.1+json' } }
response = get('/api/system_notifications', query)
response.code < Rack::Utils::SYMBOL_TO_STATUS_CODE[:bad_request]
end
private
def call_api
last_sync =
Rails.cache.fetch(SYNC_TIMESTAMP_CACHE_KEY, expires_in: 24.hours, skip_nil: true) do
SystemNotification.last_sync_timestamp
end
channel = Rails.application.secrets.system_notifications_channel
unless last_sync
@errors[:last_sync_timestamp] = 'Cannot find last_sync_timestamp'
return false
end
query = { query: { last_sync_timestamp: last_sync,
channels_slug: channel },
headers: { 'accept':
'application/vnd.system-notifications.1+json' } }
# rubocop:disable Lint/ShadowedException:
begin
@api_call = self.class.get('/api/system_notifications', query)
if @api_call.response.code.to_i != 200
@errors[:api_error] =
[@api_call.response.code.to_s, @api_call.response.message].join('-')
# Add message for logging if exists
if @api_call.parsed_response.try('error')
@errors[:api_error] += ': ' + @api_call
.parsed_response['error']
.flatten&.join(' - ').to_s
end
end
rescue SocketError, HTTParty::Error, StandardError => e
@errors[e.class.to_s.downcase.to_sym] = e.message
end
# rubocop:enable Lint/ShadowedException:
end
def save_new_notifications
received_notifications = @api_call.parsed_response['notifications']
return if received_notifications.blank?
received_notifications.each do |received_notification|
# Save new notification if not exists or override old 1
attrs = received_notification
.slice('title', 'description', 'modal_title', 'modal_body', 'show_on_login', 'source_id')
.merge('source_created_at': Time.zone.parse(received_notification['source_created_at']),
'last_time_changed_at': Time.zone.parse(received_notification['last_time_changed_at']))
.symbolize_keys
notification = SystemNotification.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
if notification.new_record?
save_notification(notification)
elsif notification.last_time_changed_at < attrs[:last_time_changed_at]
notification.update!(attrs)
end
end
Rails.cache.delete(SYNC_TIMESTAMP_CACHE_KEY)
end
def save_notification(notification)
ActiveRecord::Base.transaction do
notification.save!
User.find_in_batches do |user_ids|
user_system_notifications = user_ids.pluck(:id).collect do |item|
Hash[:user_id, item, :system_notification_id, notification.id]
end
UserSystemNotification.import user_system_notifications, validate: false
end
end
Notifications::PushToCommunicationChannelService.delay.call(item_id: notification.id,
item_type: notification.class.name)
end
end
end

View file

@ -3,7 +3,7 @@
id: 'wopi_file_edit_button',
class: 'btn btn-light',
target: '_blank' do %>
<%= file_application_icon(asset) %>
<i class="sn-icon sn-icon-<%= sn_icon_for(asset) %>"></i>
<%= wopi_button_text(asset, 'edit') %>
<% end %>
<% else %>
@ -13,7 +13,7 @@
target: '_blank',
title: title,
disabled: true do %>
<%= file_application_icon(asset) %>
<i class="sn-icon sn-icon-<%= sn_icon_for(asset) %>"></i>
<%= wopi_button_text(asset, 'edit') %>
<% end %>
<% end %>

View file

@ -58,8 +58,8 @@
</div>
</div>
<div class="search-container">
<div class="sci-input-container left-icon ">
<input type="text" class="sci-input-field task-search-field" placeholder="<%= t("dashboard.current_tasks.search") %>"></input>
<div class="sci-input-container-v2 left-icon h-[2.25rem]">
<input type="text" class="sci-input-field task-search-field" placeholder="<%= t("dashboard.current_tasks.search") %>" />
<i class="sn-icon sn-icon-search"></i>
</div>
</div>

View file

@ -17,7 +17,7 @@
end
end,
@experiment.project.id ),
{}, {class: "form-control selectpicker", "data-role" => "clear"} %>
{}, {"data-role" => "clear"} %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>

View file

@ -4,10 +4,10 @@
role="dialog"
aria-labelledby="move-experiment-modal-label">
<%= form_with model: @experiment,
url: move_experiment_path(@experiment),
method: :post,
remote: true,
html: { class: 'experiment-action-form' } do |f| %>
url: move_experiment_path(@experiment),
method: :post,
data: { remote: true },
html: { class: 'experiment-action-form' } do |f| %>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">

View file

@ -115,12 +115,6 @@
<%= render "label_printers/label_printer_modal" %>
<% end %>
<% if user_signed_in? && flash[:system_notification_modal] && current_user.show_login_system_notification? %>
<%= render partial: "/system_notifications/system_notification_modal", locals: { notification: current_user.user_system_notifications.show_on_login(true) } %>
<% else %>
<%= render partial: "/system_notifications/system_notification_modal", locals: { notification: nil} %>
<% end %>
<span style="display: none;" data-hook="application-body-end-html"></span>
<%= javascript_include_tag 'prism' %>

View file

@ -11,18 +11,16 @@
</style>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'application_pack', media: 'all' %>
<%= stylesheet_link_tag 'application_pack_styles', media: 'all' %>
<%= javascript_include_tag 'jquery_bundle' %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'application_pack_styles' %>
<%= javascript_include_tag 'application_pack' %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= favicon_link_tag "favicon.ico" %>
<%= favicon_link_tag "favicon-16.png", type: "image/png", size: "16x16" %>
<%= favicon_link_tag "favicon-32.png", type: "image/png", size: "32x32" %>
<%= favicon_link_tag "favicon-48.png", type: "image/png", size: "48x48" %>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'fontawesome' %>
<%= csrf_meta_tags %>
</head>
@ -31,7 +29,7 @@
<div class="container-fluid">
<div class="navbar-header">
<span class="navbar-brand" title="<% t('nav.label.scinote') %>">
<%= image_tag('/images/scinote_icon.svg', id: 'logo') %>
<%= image_tag('scinote_logo.svg', id: 'logo') %>
</span>
</div>
<% if user_signed_in? %>

View file

@ -11,7 +11,8 @@
<%= t('my_modules.modals.status_flow_modal.current_status') %><i class="fas fa-long-arrow-alt-right"></i>
<% end %>
</div>
<div class="status-block" style="background: <%= status[:color] %>">
<div class="status-block <%= 'btn-not-started' if status.name == 'Not started' %>"
style="background: <%= status[:color] %>">
<%= status[:name] %>
</div>
<div class="status-comment">

View file

@ -1,6 +1,6 @@
<% status = my_module.my_module_status %>
<div class="status-label">
<%= t('my_module_statuses.dropdown.status_label') %>
<div class="status-title"><%= t('my_module_statuses.dropdown.status_label') %></div>
<div class="status-note">
<% if my_module.last_transition_error %>
<div class="status-transition-error">
@ -22,7 +22,9 @@
<div class="dropdown sci-dropdown status-flow-dropdown"
data-status-changing="<%= my_module.status_changing %>"
data-status-check-url="<%= status_state_my_module_path(my_module) %>">
<button class="btn btn-secondary dropdown-toggle <%= 'disabled' if my_module.status_changing || my_module.archived_branch? %>"
<button class="btn btn-secondary dropdown-toggle
<%= 'disabled' if my_module.status_changing || my_module.archived_branch? %>
<%= 'btn-not-started' if status.name == 'Not started' %>"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
@ -45,8 +47,9 @@
<li data-state-id="<%= previous_s.id %>" id="previousStatusSelector" class="<%= 'disabled' if previous_s_errors.any? %>">
<span><%= t('my_module_statuses.dropdown.return_label') %></span>
<i class="fas fa-long-arrow-alt-right"></i>
<div class=" status-container">
<div class="status-name" style="<%= "background-color: #{previous_s.color}" %>"><%= previous_s.name %></div>
<div class="status-container">
<div class="status-name <%= 'btn-not-started' if previous_s.name == 'Not started' %>"
style="<%= "background-color: #{previous_s.color}" %>"><%= previous_s.name %></div>
</div>
</li>
<span class="error-message"><% previous_s_errors.each do |error| %>

View file

@ -17,14 +17,14 @@
<div class="checkbox">
<%= f.check_box :visibility,
{ label: t('projects.index.modal_new_project.visibility_html'),
data: { action: 'toggle-visibility', target: 'role_select_wrapper' },
data: { action: 'toggle-visibility', target: 'edit_project_role_select_wrapper' },
checked: f.object.visible? },
:visible,
:hidden %>
</div>
</div>
</div>
<div class="row <%= f.object.hidden? ? 'hidden' : '' %>" id="role_select_wrapper">
<div class="row <%= f.object.hidden? ? 'hidden' : '' %>" id="edit_project_role_select_wrapper">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<%= f.select :default_public_user_role_id,
options_for_select(user_roles_collection(@project), selected: f.object.default_public_user_role_id),

View file

@ -8,7 +8,7 @@
<div class="modal-body">
<%= t('projects.index.modal_move_folder.description', items: items_label) %>
<div class="sci-input-container left-icon">
<div class="sci-input-container-v2 left-icon my-4 h-[2.25rem]">
<input type="search" class="form-control sci-input-field" id="searchFolderTree" placeholder="Find a folder"/>
<i class="sn-icon sn-icon-search"></i>
</div>

View file

@ -19,7 +19,7 @@
<div class="flex items-center gap-4 text-xs">
<div class="sci-checkbox-container">
<%= f.check_box :visibility,
{ class: 'sci-checkbox' ,data: { action: 'toggle-visibility', target: 'role_select_wrapper' } },
{ class: 'sci-checkbox' ,data: { action: 'toggle-visibility', target: 'new_project_role_select_wrapper' } },
:visible,
:hidden %>
<span class="sci-checkbox-label"></span>
@ -28,7 +28,7 @@
</div>
</div>
</div>
<div class="row hidden" id="role_select_wrapper">
<div class="row hidden" id="new_project_role_select_wrapper">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 sci-input-container mt-4">
<%= f.select :default_public_user_role_id,
options_for_select(user_roles_collection(@project), UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id),

View file

@ -5,7 +5,7 @@
<%= wicked_pdf_stylesheet_link_tag "application" %>
<%= wicked_pdf_stylesheet_link_tag "reports_pdf" %>
<%= font_awesome_cdn_link_tag %>
<%= wicked_pdf_javascript_include_tag "jquery" %>
<%= wicked_pdf_javascript_include_tag "jquery_bundle" %>
<%= wicked_pdf_javascript_include_tag "handsontable.full" %>
<!-- Libraries for formulas -->
<%= wicked_pdf_javascript_include_tag "lodash" %>

View file

@ -1,5 +1,5 @@
<div class="modal" id="create-repo-modal" tabindex="-1" role="dialog" aria-labelledby="create-repo-modal-label">
<%= form_with url: [current_team, @repository], remote: :true do |f| %>
<%= form_with model: @repository, url: repositories_path, html: { data: { remote: true } } do |f| %>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">

View file

@ -1,6 +1,9 @@
<div class="well">
<%= form_with(model: @result, url: result_asset_path(format: :json), data: { remote: true }) do |f| %>
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<%= form_with(model: @result, url: result_asset_path(format: :json), data: { remote: true }, html: { class: 'edit-result-asset' }) do |f| %>
<div class="sci-input-container mb-4">
<label><%= t('result_assets.edit.label.name') %></label>
<%= f.text_field :name, class: 'sci-input-field' %>
</div>
<%= f.fields_for :asset do |ff| %>
<span><strong><%=t "result_assets.edit.uploaded_asset" %></strong></span>
<p style="margin: 10px;">
@ -18,5 +21,4 @@
class: 'btn btn-primary save-result' %>
</div>
<% end %>
<%= javascript_include_tag 'results/result_assets/edit' %>
</div>

View file

@ -27,5 +27,4 @@
</div>
<% end %>
<%= javascript_include_tag 'results/result_assets/new' %>
</div>

View file

@ -1,7 +1,10 @@
<div id="table-form" class="well">
<%= form_with(model: @result, url: result_table_path(format: :json),
data: { remote: true, 'name-max-length': Constants::NAME_MAX_LENGTH }) do |f| %>
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<div class="sci-input-container mb-4">
<label><%= t('result_tables.edit.label.name') %></label>
<%= f.text_field :name, class: 'sci-input-field' %>
</div>
<div class="editable-table">
<%= f.fields_for :table do |ff| %>
<%= ff.hidden_field(:contents, value: ff.object.contents_utf_8, class: "hot-contents" ) %>
@ -17,7 +20,6 @@
</button>
<%= f.button t("general.save"),
class: 'btn btn-primary save-result' %>
<%= javascript_include_tag 'results/result_tables/edit' %>
</div>
<% end %>
</div>

View file

@ -1,7 +1,10 @@
<div id="table-form" class="well">
<%= form_with(model: @result, url: my_module_result_tables_path(format: :json, page: params[:page], order: params[:order]),
data: { remote: true, 'name-max-length': Constants::NAME_MAX_LENGTH }) do |f| %>
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<div class="sci-input-container mb-4">
<label><%= t('result_tables.new.label.name') %></label>
<%= f.text_field :name, class: 'sci-input-field' %>
</div>
<div class="editable-table" style="margin-bottom: 25px;">
<%= f.fields_for :table do |ff| %>
<%= ff.hidden_field(:contents, value: ff.object.contents, class: "hot-contents" ) %>
@ -16,7 +19,6 @@
</button>
<%= f.button t("result_tables.new.create"),
class: 'btn btn-primary save-result' %>
<%= javascript_include_tag 'results/result_tables/new' %>
</div>
<% end %>
</div>

View file

@ -1,5 +1,5 @@
<div class="text-3xl font-bold flex flex-row flex-nowrap">
<span class="inline-block whitespace-nowrap">
<div class="text-3xl font-semibold flex flex-row flex-nowrap">
<span class="inline-block whitespace-nowrap mr-1">
<%= t "labels.archived" if @my_module.archived? %>
</span>
<span class="inline-block truncate w-[calc(100vw-27rem)]" title="<%= @my_module.name %>">

View file

@ -1,4 +1,4 @@
<div class="text-3xl font-bold"><%= @my_module.name %></div>
<div class="text-3xl font-semibold"><%= @my_module.name %></div>
<div class="px-4 my-5 flex-1 bg-sn-white">
<div class="content-pane flexible">

View file

@ -1,4 +1,4 @@
<span class="due-date-label <%= get_task_alert_color(my_module) %>">
<span class="due-date-label font-medium <%= get_task_alert_color(my_module) %>">
<% if (my_module.archived_branch? || my_module.completed? ) && my_module.due_date %>
<span class="iso-formatted-date"><%= my_module.due_date.iso8601 %></span>
<% elsif my_module.is_one_day_prior? %>

View file

@ -1,4 +1,4 @@
<div class="flex items-center uppercase text-bold">
<div class="flex items-center uppercase">
<a class="p-4 border-b-4 border-transparent hover:no-underline capitalize <%= is_module_protocols? ? "text-sn-blue" : "text-sn-grey" %>"
href="<%= shared_protocol_url(@shareable_link.uuid) %>"
title="<%= t("nav2.modules.steps") %>"

View file

@ -5,7 +5,7 @@
<%= t("shareable_links.left_navigation.welcome")%>
</h3>
<div class="flex flex-col p-6 bg-sn-super-light-blue gap-4">
<span class="font-bold">
<span class="font-semibold">
<%= t("shareable_links.left_navigation.title")%>
</span>
<span class="text-[.75rem]">

View file

@ -33,7 +33,7 @@
<span class="hidden-xs hidden-sm hidden-md"><%= t('my_modules.details.completed_date') %></span>
</div>
<div class="datetime-container">
<span class="date-text iso-formatted-date">
<span class="iso-formatted-date font-medium">
<%= @my_module.completed_on.iso8601 %>
</span>
</div>

View file

@ -1,5 +1,5 @@
<span class="start-date-label">
<% if my_module.started_on.present? %>
<span class="iso-formatted-date font-bold"><%= my_module.started_on.iso8601 %></span>
<span class="iso-formatted-date font-medium"><%= my_module.started_on.iso8601 %></span>
<% end %>
</span>

View file

@ -104,4 +104,4 @@
<% end %>
</div>
<%= javascript_include_tag 'shared/file_preview' %>
<%= javascript_include_tag 'shared/file_preview', nonce: true %>

View file

@ -10,7 +10,7 @@
<div class="sci--navigation--top-menu-logo">
<a title="SciNote" href="/">
<%= image_tag "/images/sn-icon.svg", class: "logo small" %>
<%= image_tag "/images/scinote_icon.svg", class: "logo large" %>
<%= image_tag "scinote_logo.svg", class: "logo large" %>
</a>
</div>
</div>

View file

@ -1,10 +0,0 @@
<p>
<%= I18n.t("system_notifications.emails.intro_paragraph", user_name: @user.name) %>
</p>
<p>
<%= link_to @system_notification.title.html_safe, system_notifications_url %>
</p>
<p>
<%= @system_notification.description.html_safe %>
</p>

View file

@ -19,6 +19,7 @@
<div class="form-group" style="max-width: 500px;">
<%= f.text_area :description, label: t('users.settings.teams.new.description_label') %>
<br>
<small>
<%=t 'users.settings.teams.new.description_sublabel' %>
</small>

View file

@ -65,7 +65,7 @@
</span>
</div>
<div class="team-description">
<div class="team-description inline-editing-container">
<% if can_manage_team?(@team) %>
<div
class="inline-init-handler"
@ -77,8 +77,9 @@
<div class="view-mode" data-placeholder="<%= t("users.settings.teams.show.enter_description") %>"><%= @team.description %></div>
<textarea placeholder="<%= t("users.settings.teams.show.enter_description") %>" class="hidden input-field smart-text-area" type="text" value="<%= @team.description %>" disabled><%= @team.description %></textarea>
<div class="button-container">
<span class="cancel-button inline-field-button"><%= t('general.cancel') %></span>
<span class="save-button inline-field-button"><%= t('general.save') %></span>
<span class="cancel-button inline-field-button"><i class="sn-icon sn-icon-close"></i></span>
<span class="save-button inline-field-button"><i class="sn-icon sn-icon-check"></i></span>
</div>
</div>
<% else %>

View file

@ -1,8 +1,7 @@
<%= form_with model: user_assignment,
url: destroy_user_team_path(user_assignment, format: :json),
data: { remote: true },
method: :delete,
data: { id: 'destroy-user-team-form' } do |f| %>
data: { remote: true, id: 'destroy-user-team-form' },
method: :delete do |f| %>
<p><%= t("users.settings.user_teams.destroy_uo_message",
user: user_assignment.user.full_name,
team: user_assignment.assignable.name) %></p>

Some files were not shown because too many files have changed in this diff Show more