mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-25 09:13:05 +08:00
Merge branch 'develop' into ok_SCI_3988
This commit is contained in:
commit
522ae12f08
195 changed files with 10090 additions and 2135 deletions
|
@ -1,6 +1,6 @@
|
|||
ruby:
|
||||
config_file: .rubocop.yml
|
||||
version: 0.72.0
|
||||
version: 0.75.0
|
||||
|
||||
eslint:
|
||||
enabled: true
|
||||
|
|
18
.rubocop.yml
18
.rubocop.yml
|
@ -83,7 +83,7 @@ Naming/FileName:
|
|||
Enabled: false
|
||||
Exclude: []
|
||||
|
||||
Layout/FirstParameterIndentation:
|
||||
Layout/FirstArgumentIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Style/For:
|
||||
|
@ -364,14 +364,6 @@ Metrics/ModuleLength:
|
|||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Max: 120
|
||||
AllowHeredoc: true
|
||||
AllowURI: true
|
||||
URISchemes:
|
||||
- http
|
||||
- https
|
||||
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
|
@ -392,6 +384,14 @@ Layout/EndAlignment:
|
|||
Layout/DefEndAlignment:
|
||||
EnforcedStyleAlignWith: start_of_line
|
||||
|
||||
Layout/LineLength:
|
||||
Max: 120
|
||||
AllowHeredoc: true
|
||||
AllowURI: true
|
||||
URISchemes:
|
||||
- http
|
||||
- https
|
||||
|
||||
##################### Lint #####################################
|
||||
|
||||
Lint/AssignmentInCondition:
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -122,7 +122,7 @@ group :development, :test do
|
|||
gem 'pry-rails'
|
||||
gem 'rails-controller-testing'
|
||||
gem 'rspec-rails', '>= 4.0.0.beta2'
|
||||
gem 'rubocop', '>= 0.59.0', require: false
|
||||
gem 'rubocop', '>= 0.75.0', require: false
|
||||
gem 'rubocop-performance'
|
||||
gem 'rubocop-rails'
|
||||
gem 'timecop'
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -287,7 +287,7 @@ GEM
|
|||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.13, < 3)
|
||||
iniparse (1.4.4)
|
||||
jaro_winkler (1.5.3)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
jmespath (1.4.0)
|
||||
|
@ -387,8 +387,8 @@ GEM
|
|||
overcommit (0.49.1)
|
||||
childprocess (>= 0.6.3, < 2.0)
|
||||
iniparse (~> 1.4)
|
||||
parallel (1.17.0)
|
||||
parser (2.6.4.0)
|
||||
parallel (1.19.1)
|
||||
parser (2.6.5.0)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.1.4)
|
||||
pg_search (2.3.0)
|
||||
|
@ -486,16 +486,16 @@ GEM
|
|||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.8.2)
|
||||
rubocop (0.74.0)
|
||||
rubocop (0.78.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-performance (1.4.1)
|
||||
rubocop-performance (1.5.1)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.3.2)
|
||||
rubocop-rails (2.4.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-graphviz (1.2.4)
|
||||
|
@ -669,7 +669,7 @@ DEPENDENCIES
|
|||
rgl
|
||||
roo (~> 2.8.2)
|
||||
rspec-rails (>= 4.0.0.beta2)
|
||||
rubocop (>= 0.59.0)
|
||||
rubocop (>= 0.75.0)
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
ruby-graphviz (~> 1.2)
|
||||
|
|
|
@ -29,20 +29,24 @@
|
|||
//= require jsPlumb-2.0.4-min
|
||||
//= require jsnetworkx
|
||||
//= require bootstrap-select
|
||||
//= require_directory ./sitewide
|
||||
//= require_directory ./repository_columns/columns_initializers
|
||||
//= require datatables
|
||||
//= require ajax-bootstrap-select.min
|
||||
//= require underscore
|
||||
//= require i18n.js
|
||||
//= require i18n/translations
|
||||
//= require users/settings/teams/invite_users_modal
|
||||
//= require repository_columns/index
|
||||
//= require perfect-scrollbar.min
|
||||
//= require shared/inline_editing
|
||||
//= require activestorage
|
||||
//= require global_activities/side_pane
|
||||
//= require protocols/header
|
||||
//= require turbolinks
|
||||
//= require marvinjslauncher
|
||||
//= require_tree ./repositories/renderers
|
||||
//= require_directory ./repositories/validators
|
||||
//= require_directory ./sitewide
|
||||
//= require turbolinks
|
||||
|
||||
|
||||
// Initialize links for submitting forms. This is useful for submitting
|
||||
|
|
|
@ -1,396 +1,260 @@
|
|||
/* global Promise _ ActiveStorage RepositoryItemEditForm */
|
||||
|
||||
//= require sugar.min
|
||||
//= require jquerymy-1.2.14.min
|
||||
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* RepositoryItemEditForm generates the html inputs for
|
||||
* repository item and returns the form data object
|
||||
*
|
||||
* @param {Object} itemData - repository item data fetched from the API
|
||||
* @param {Object} repositoryItemElement - row node in the table
|
||||
*/
|
||||
global.RepositoryItemEditForm = function(itemData, repositoryItemElement) {
|
||||
this.itemData = itemData;
|
||||
this.repositoryItemElement = repositoryItemElement;
|
||||
this.formData = this.composeFormData(itemData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the input fields
|
||||
*
|
||||
* @param {Object} table - datatable.js object
|
||||
* @returns {undefinded}
|
||||
*/
|
||||
RepositoryItemEditForm.prototype.renderForm = function(table) {
|
||||
var colIndex = getColumnIndex(table, '#row-name');
|
||||
var cells = this.itemData.repository_row.repository_cells;
|
||||
var listColumns = this.itemData.repository_row.repository_column_items;
|
||||
var formData = this.formData;
|
||||
|
||||
if (colIndex) {
|
||||
$(this.repositoryItemElement).children('td').eq(colIndex)
|
||||
.html(changeToInputField('repository_row',
|
||||
'name',
|
||||
this.itemData.repository_row.name,
|
||||
'rowName'));
|
||||
}
|
||||
|
||||
$(this.repositoryItemElement).children('td').each(function(i) {
|
||||
var td = $(this);
|
||||
var rawIndex = table.column.index('fromVisible', i);
|
||||
var colHeader = table.column(rawIndex).header();
|
||||
|
||||
if ($(colHeader).hasClass('repository-column')) {
|
||||
var type = $(colHeader).attr('data-type');
|
||||
var colHeaderId = $(colHeader).attr('id');
|
||||
var cell = cells[colHeaderId] || '';
|
||||
td.html(changeToFormField('repository_cell',
|
||||
colHeaderId,
|
||||
type,
|
||||
cell,
|
||||
listColumns));
|
||||
addSelectedFile(type, colHeaderId);
|
||||
appendNewElementToFormData(cell, colHeaderId, formData);
|
||||
}
|
||||
});
|
||||
initializeDataBinding(this.repositoryItemElement, formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse received data in to a from object
|
||||
*
|
||||
* @param {Object} itemData - json representations of repository item
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
RepositoryItemEditForm.prototype.composeFormData = function(itemData) {
|
||||
var formBindingsData = {};
|
||||
formBindingsData['rowName'] = itemData.repository_row.name;
|
||||
$.each(itemData.repository_row.repository_cells, function(i, cell) {
|
||||
var tableCellId = 'colId-' + cell.cell_column_id;
|
||||
if(cell.type === 'RepositoryAssetValue') {
|
||||
formBindingsData[tableCellId] = new File([""], cell.value.file_name);
|
||||
} else {
|
||||
formBindingsData[tableCellId] = cell.value;
|
||||
}
|
||||
});
|
||||
return formBindingsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles select picker default value
|
||||
*
|
||||
* @param {Object} node
|
||||
* @returns {undefinded}
|
||||
*/
|
||||
RepositoryItemEditForm.prototype.initializeSelectpickerValues = function(node) {
|
||||
$($(node).find('.bootstrap-select')).each(function(_, dropdown) {
|
||||
var selectedValue = $($(dropdown).find('select')[0]).data('selected-value');
|
||||
var selectPicker = $($(dropdown).find('select')[0]);
|
||||
var value = '-1'
|
||||
$(dropdown).find('option').each(function(_, option) {
|
||||
$(option).removeAttr('selected');
|
||||
if($(option).val() === selectedValue.toString()) {
|
||||
$(option).attr('selected', true);
|
||||
value = $(option).attr('value');
|
||||
}
|
||||
});
|
||||
$(dropdown).parent().attr("list_item_id", value);
|
||||
selectPicker.val(value);
|
||||
selectPicker.selectpicker('refresh');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a FormData object with the repository row data ready to be
|
||||
* sended on the server
|
||||
*
|
||||
* @param {Object} tableID
|
||||
* @param {Object} selectedRecord
|
||||
*
|
||||
* @returns (Object)
|
||||
*/
|
||||
RepositoryItemEditForm.prototype.parseToFormObject = function(tableID, selectedRecord) {
|
||||
var formData = this.formData;
|
||||
var formDataObj = new FormData();
|
||||
var removeFileColumns = [];
|
||||
var filesToUploadCntr = 0;
|
||||
var filesUploadedCntr = 0;
|
||||
const directUploadUrl = $(tableID).data('directUploadUrl');
|
||||
|
||||
formDataObj.append('request_url', $(tableID).data('current-uri'));
|
||||
formDataObj.append('repository_row_id', $(selectedRecord).attr('id'));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$(_.keys(this.formData)).each(function(_, element) {
|
||||
var value = formData[element];
|
||||
if (element === 'rowName') {
|
||||
formDataObj.append('repository_row_name', value);
|
||||
} else {
|
||||
let colId = element.replace('colId-', '');
|
||||
let $el = $('#' + element);
|
||||
// don't save anything if element is not visible
|
||||
if ($el.length === 0) {
|
||||
return;
|
||||
}
|
||||
if ($el.attr('type') === 'file') {
|
||||
// handle deleting of element
|
||||
if ($el.attr('remove') === 'true') {
|
||||
removeFileColumns.push(colId);
|
||||
formDataObj.append('repository_cells[' + colId + ']', null);
|
||||
} else if ($el[0].files.length > 0) {
|
||||
filesToUploadCntr += 1;
|
||||
}
|
||||
} else if (value.length >= 0) {
|
||||
formDataObj.append('repository_cells[' + colId + ']', value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns));
|
||||
|
||||
// No files for upload, so return earlier
|
||||
if (filesToUploadCntr === 0) {
|
||||
resolve(formDataObj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Second run, just for files
|
||||
$(_.keys(this.formData)).each(function(_, element) {
|
||||
let $el = $('#' + element);
|
||||
let colId = element.replace('colId-', '');
|
||||
|
||||
if ($el.attr('type') === 'file' && $el.attr('remove') !== 'true') {
|
||||
let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl);
|
||||
|
||||
upload.create(function(error, blob) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
formDataObj.append('repository_cells[' + colId + ']', blob.signed_id);
|
||||
filesUploadedCntr += 1;
|
||||
|
||||
if (filesUploadedCntr === filesToUploadCntr) {
|
||||
resolve(formDataObj);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* |-----------------|
|
||||
* | Private methods |
|
||||
* |-----------------|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Takes object and surrounds it with input
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
* @param {String} id
|
||||
*
|
||||
* @returns (String)
|
||||
*/
|
||||
function changeToInputField(object, name, value, id) {
|
||||
return "<div class='form-group'><input class='form-control' data-object='"
|
||||
+ object + "' name='" + name + "' value='" + value + "' id='" + id + "'></input></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes object and creates an input file field, contains a hidden
|
||||
* input field which is triggered on button click and we get the uploaded
|
||||
* file from there.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
* @param {String} id
|
||||
*
|
||||
* @returns (String)
|
||||
*/
|
||||
function changeToInputFileField(object, name, value, id) {
|
||||
var fileName = (value.file_name) ? value.file_name : I18n.t('general.file.no_file_chosen');
|
||||
var buttonLabel = I18n.t('general.file.choose');
|
||||
var html = "<div class='repository-input-file-field'>" +
|
||||
"<div class='form-group'><div><input type='file' name='" + name + "' id='" +
|
||||
id + "' style='display:none' /><button class='btn btn-default' " +
|
||||
"data-object='" + object + "' name='" + name + "' value='" + value +
|
||||
"' data-id='" + id + "'>" + buttonLabel +
|
||||
"</button></div><div><p class='file-name-label'>" + truncateLongString(fileName, 20) +
|
||||
"</p></div>";
|
||||
if(value.file_name) {
|
||||
html += "<div><a data-action='removeAsset' ";
|
||||
html += "onClick='clearFileInput(this)'><i class='fas fa-times'></i></a>";
|
||||
} else {
|
||||
html += "<div><a data-action='removeAsset' onClick='clearFileInput(this)' ";
|
||||
html += "style='display:none'><i class='fas fa-times'></i></a>";
|
||||
}
|
||||
html += "</div></div></div>";
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the colum index
|
||||
*
|
||||
* @param {Object} table
|
||||
* @param {String} id
|
||||
*
|
||||
* @returns (Boolean | Number)
|
||||
*/
|
||||
function getColumnIndex(table, id) {
|
||||
if(id < 0)
|
||||
return false;
|
||||
return table.column(id).index('visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Genrates list items dropdown element
|
||||
*
|
||||
* @param {Array} options
|
||||
* @param {String} current_value
|
||||
* @param {Number} columnId
|
||||
* @param {String} id
|
||||
*
|
||||
* @returns (String)
|
||||
*/
|
||||
function _listItemDropdown(options, current_value, columnId, id) {
|
||||
var val = undefined;
|
||||
var html = '<select id="' + id + '" class="form-control selectpicker repository-dropdown" ';
|
||||
html += 'data-selected-value="" data-abs-min-length="2" data-live-search="true" ';
|
||||
html += 'data-container="body" column_id="' + columnId +'">';
|
||||
html += '<option value="-1"></option>';
|
||||
$.each(options, function(index, value) {
|
||||
var selected = '';
|
||||
if (current_value === value[1]) {
|
||||
selected = 'selected';
|
||||
val = value[0];
|
||||
}
|
||||
html += '<option value="' + value[0] + '" ' + selected + '>';
|
||||
html += value[1] + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
return (val) ? $(html).attr('data-selected-value', val)[0] : html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an object and creates custom html element
|
||||
*
|
||||
* @param {String} object
|
||||
* @param {String} name
|
||||
* @param {String} column_type
|
||||
* @param {Object} cell
|
||||
* @param {Object} listColumns
|
||||
*
|
||||
* @returns (String)
|
||||
*/
|
||||
function changeToFormField(object, name, column_type, cell, listColumns) {
|
||||
var cellId = generateInputFieldReference(name);
|
||||
var value = cell.value || '';
|
||||
if (column_type === 'RepositoryListValue') {
|
||||
var column = _.findWhere(listColumns,
|
||||
{ column_id: parseInt(name, 10) });
|
||||
var list_items = column.list_items || cell.list_items;
|
||||
return _listItemDropdown(list_items, value, parseInt(name, 10), cellId);
|
||||
} else if (column_type === 'RepositoryAssetValue') {
|
||||
return changeToInputFileField('repository_cell_file', name, value, cellId);
|
||||
} else {
|
||||
return changeToInputField(object, name, value, cellId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the change listener to file field
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} name
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function addSelectedFile(type, name) {
|
||||
var button = $('button[data-id="' +
|
||||
generateInputFieldReference(name) +
|
||||
'"]');
|
||||
if (type === 'RepositoryAssetValue') {
|
||||
var fileInput = $(button.parent().find('input[type="file"]')[0]);
|
||||
button.on('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
fileInput.trigger('click');
|
||||
initFileHandler(fileInput);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle extraction of file from the input field
|
||||
*
|
||||
* @param {Object} $inputField
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function initFileHandler($inputField) {
|
||||
$inputField.on('change', function() {
|
||||
var input = $(this);
|
||||
var $label = $($(this).closest('.repository-input-file-field')
|
||||
.find('.file-name-label')[0]);
|
||||
var file = this.files[0];
|
||||
if (file) {
|
||||
$label.text(truncateLongString(file.name, 20));
|
||||
input.attr('remove', false);
|
||||
$($label.closest('.repository-input-file-field')
|
||||
.find('[data-action="removeAsset"]')[0]).show();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the data binding for form object
|
||||
*
|
||||
* @param {Object} rowNode
|
||||
* @param {Object} data
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function initializeDataBinding(rowNode, data) {
|
||||
var uiBindings = {};
|
||||
$.each(_.keys(data), function(i, element) {
|
||||
uiBindings['#' + element] = element;
|
||||
})
|
||||
$(rowNode).my({ui: uiBindings}, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the input tag id that will be used in the formData object
|
||||
*
|
||||
* @param {String} columnId
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
function generateInputFieldReference(columnId) {
|
||||
return 'colId-' + columnId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends aditional fields to form data object
|
||||
* @param {Object} cell
|
||||
* @param {String} columnId
|
||||
* @param {Object} formData
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function appendNewElementToFormData(cell, columnId, formData) {
|
||||
if (!cell.repository_cell_id) {
|
||||
formData[generateInputFieldReference(columnId)] = undefined;
|
||||
}
|
||||
}
|
||||
}(window));
|
||||
// /* global Promise _ ActiveStorage RepositoryItemEditForm */
|
||||
//
|
||||
// //= require sugar.min
|
||||
// //= require jquerymy-1.2.14.min
|
||||
//
|
||||
// (function(global) {
|
||||
// 'use strict';
|
||||
//
|
||||
// /**
|
||||
// * Creates a FormData object with the repository row data ready to be
|
||||
// * sended on the server
|
||||
// *
|
||||
// * @param {Object} tableID
|
||||
// * @param {Object} selectedRecord
|
||||
// *
|
||||
// * @returns (Object)
|
||||
// */
|
||||
// RepositoryItemEditForm.prototype.parseToFormObject = function(tableID, selectedRecord) {
|
||||
// var formData = this.formData;
|
||||
// var formDataObj = new FormData();
|
||||
// var removeFileColumns = [];
|
||||
// var filesToUploadCntr = 0;
|
||||
// var filesUploadedCntr = 0;
|
||||
// const directUploadUrl = $(tableID).data('directUploadUrl');
|
||||
//
|
||||
// formDataObj.append('request_url', $(tableID).data('current-uri'));
|
||||
// formDataObj.append('repository_row_id', $(selectedRecord).attr('id'));
|
||||
//
|
||||
// return new Promise((resolve, reject) => {
|
||||
// $(_.keys(this.formData)).each(function(_, element) {
|
||||
// var value = formData[element];
|
||||
// if (element === 'rowName') {
|
||||
// formDataObj.append('repository_row_name', value);
|
||||
// } else {
|
||||
// let colId = element.replace('colId-', '');
|
||||
// let $el = $('#' + element);
|
||||
// // don't save anything if element is not visible
|
||||
// if ($el.length === 0) {
|
||||
// return;
|
||||
// }
|
||||
// if ($el.attr('type') === 'file') {
|
||||
// // handle deleting of element
|
||||
// if ($el.attr('remove') === 'true') {
|
||||
// removeFileColumns.push(colId);
|
||||
// formDataObj.append('repository_cells[' + colId + ']', null);
|
||||
// } else if ($el[0].files.length > 0) {
|
||||
// filesToUploadCntr += 1;
|
||||
// }
|
||||
// } else if (value.length >= 0) {
|
||||
// formDataObj.append('repository_cells[' + colId + ']', value);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns));
|
||||
//
|
||||
// // No files for upload, so return earlier
|
||||
// if (filesToUploadCntr === 0) {
|
||||
// resolve(formDataObj);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Second run, just for files
|
||||
// $(_.keys(this.formData)).each(function(_, element) {
|
||||
// let $el = $('#' + element);
|
||||
// let colId = element.replace('colId-', '');
|
||||
//
|
||||
// if ($el.attr('type') === 'file' && $el.attr('remove') !== 'true') {
|
||||
// let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl);
|
||||
//
|
||||
// upload.create(function(error, blob) {
|
||||
// if (error) {
|
||||
// reject(error);
|
||||
// } else {
|
||||
// formDataObj.append('repository_cells[' + colId + ']', blob.signed_id);
|
||||
// filesUploadedCntr += 1;
|
||||
//
|
||||
// if (filesUploadedCntr === filesToUploadCntr) {
|
||||
// resolve(formDataObj);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * Takes object and creates an input file field, contains a hidden
|
||||
// * input field which is triggered on button click and we get the uploaded
|
||||
// * file from there.
|
||||
// *
|
||||
// * @param {Object} object
|
||||
// * @param {String} name
|
||||
// * @param {String} value
|
||||
// * @param {String} id
|
||||
// *
|
||||
// * @returns (String)
|
||||
// */
|
||||
// function changeToInputFileField(object, name, value, id) {
|
||||
// var fileName = (value.file_name) ? value.file_name : I18n.t('general.file.no_file_chosen');
|
||||
// var buttonLabel = I18n.t('general.file.choose');
|
||||
// var html = "<div class='repository-input-file-field'>" +
|
||||
// "<div class='form-group'><div><input type='file' name='" + name + "' id='" +
|
||||
// id + "' style='display:none' /><button class='btn btn-default' " +
|
||||
// "data-object='" + object + "' name='" + name + "' value='" + value +
|
||||
// "' data-id='" + id + "'>" + buttonLabel +
|
||||
// "</button></div><div><p class='file-name-label'>" + truncateLongString(fileName, 20) +
|
||||
// "</p></div>";
|
||||
// if(value.file_name) {
|
||||
// html += "<div><a data-action='removeAsset' ";
|
||||
// html += "onClick='clearFileInput(this)'><i class='fas fa-times'></i></a>";
|
||||
// } else {
|
||||
// html += "<div><a data-action='removeAsset' onClick='clearFileInput(this)' ";
|
||||
// html += "style='display:none'><i class='fas fa-times'></i></a>";
|
||||
// }
|
||||
// html += "</div></div></div>";
|
||||
//
|
||||
// return html;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Returns the colum index
|
||||
// *
|
||||
// * @param {Object} table
|
||||
// * @param {String} id
|
||||
// *
|
||||
// * @returns (Boolean | Number)
|
||||
// */
|
||||
// function getColumnIndex(table, id) {
|
||||
// if(id < 0)
|
||||
// return false;
|
||||
// return table.column(id).index('visible');
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Genrates list items dropdown element
|
||||
// *
|
||||
// * @param {Array} options
|
||||
// * @param {String} current_value
|
||||
// * @param {Number} columnId
|
||||
// * @param {String} id
|
||||
// *
|
||||
// * @returns (String)
|
||||
// */
|
||||
// function _listItemDropdown(options, current_value, columnId, id) {
|
||||
// var val = undefined;
|
||||
// var html = '<select id="' + id + '" class="form-control selectpicker repository-dropdown" ';
|
||||
// html += 'data-selected-value="" data-abs-min-length="2" data-live-search="true" ';
|
||||
// html += 'data-container="body" column_id="' + columnId +'">';
|
||||
// html += '<option value="-1"></option>';
|
||||
// $.each(options, function(index, value) {
|
||||
// var selected = '';
|
||||
// if (current_value === value[1]) {
|
||||
// selected = 'selected';
|
||||
// val = value[0];
|
||||
// }
|
||||
// html += '<option value="' + value[0] + '" ' + selected + '>';
|
||||
// html += value[1] + '</option>';
|
||||
// });
|
||||
// html += '</select>';
|
||||
// return (val) ? $(html).attr('data-selected-value', val)[0] : html;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Takes an object and creates custom html element
|
||||
// *
|
||||
// * @param {String} object
|
||||
// * @param {String} name
|
||||
// * @param {String} column_type
|
||||
// * @param {Object} cell
|
||||
// * @param {Object} listColumns
|
||||
// *
|
||||
// * @returns (String)
|
||||
// */
|
||||
// function changeToFormField(object, name, column_type, cell, listColumns) {
|
||||
// var cellId = generateInputFieldReference(name);
|
||||
// var value = cell.value || '';
|
||||
// if (column_type === 'RepositoryListValue') {
|
||||
// var column = _.findWhere(listColumns,
|
||||
// { column_id: parseInt(name, 10) });
|
||||
// var list_items = column.list_items || cell.list_items;
|
||||
// return _listItemDropdown(list_items, value, parseInt(name, 10), cellId);
|
||||
// } else if (column_type === 'RepositoryAssetValue') {
|
||||
// return changeToInputFileField('repository_cell_file', name, value, cellId);
|
||||
// } else {
|
||||
// return changeToInputField(object, name, value, cellId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Append the change listener to file field
|
||||
// *
|
||||
// * @param {String} type
|
||||
// * @param {String} name
|
||||
// *
|
||||
// * @returns {undefined}
|
||||
// */
|
||||
// function addSelectedFile(type, name) {
|
||||
// var button = $('button[data-id="' +
|
||||
// generateInputFieldReference(name) +
|
||||
// '"]');
|
||||
// if (type === 'RepositoryAssetValue') {
|
||||
// var fileInput = $(button.parent().find('input[type="file"]')[0]);
|
||||
// button.on('click', function(ev) {
|
||||
// ev.preventDefault();
|
||||
// ev.stopPropagation();
|
||||
// fileInput.trigger('click');
|
||||
// initFileHandler(fileInput);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Handle extraction of file from the input field
|
||||
// *
|
||||
// * @param {Object} $inputField
|
||||
// *
|
||||
// * @returns {undefined}
|
||||
// */
|
||||
// function initFileHandler($inputField) {
|
||||
// $inputField.on('change', function() {
|
||||
// var input = $(this);
|
||||
// var $label = $($(this).closest('.repository-input-file-field')
|
||||
// .find('.file-name-label')[0]);
|
||||
// var file = this.files[0];
|
||||
// if (file) {
|
||||
// $label.text(truncateLongString(file.name, 20));
|
||||
// input.attr('remove', false);
|
||||
// $($label.closest('.repository-input-file-field')
|
||||
// .find('[data-action="removeAsset"]')[0]).show();
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Generates the input tag id that will be used in the formData object
|
||||
// *
|
||||
// * @param {String} columnId
|
||||
// *
|
||||
// * @returns {String}
|
||||
// */
|
||||
// function generateInputFieldReference(columnId) {
|
||||
// return 'colId-' + columnId;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Appends aditional fields to form data object
|
||||
// * @param {Object} cell
|
||||
// * @param {String} columnId
|
||||
// * @param {Object} formData
|
||||
// *
|
||||
// * @returns {undefined}
|
||||
// */
|
||||
// function appendNewElementToFormData(cell, columnId, formData) {
|
||||
// if (!cell.repository_cell_id) {
|
||||
// formData[generateInputFieldReference(columnId)] = undefined;
|
||||
// }
|
||||
// }
|
||||
// }(window));
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
global ActiveStorage Promise
|
||||
*/
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var Asset = (function() {
|
||||
function uploadFiles($fileInputs, directUploadUrl) {
|
||||
let filesToUploadCntr = 0;
|
||||
let filesUploadedCntr = 0;
|
||||
let filesForUpload = [];
|
||||
|
||||
$fileInputs.each(function(_, f) {
|
||||
let $f = $(f);
|
||||
if ($f.val()) {
|
||||
filesToUploadCntr += 1;
|
||||
filesForUpload.push($f);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (filesToUploadCntr === 0) {
|
||||
resolve('done');
|
||||
return;
|
||||
}
|
||||
|
||||
$(filesForUpload).each(function(_, $el) {
|
||||
let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl);
|
||||
|
||||
upload.create(function(error, blob) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
$el
|
||||
.prev('.file-hidden-field-container')
|
||||
.html(`<input type="hidden"
|
||||
form="${$el.attr('form')}"
|
||||
name="repository_cells[${$el.data('col-id')}]"
|
||||
value="${blob.signed_id}"/>`);
|
||||
|
||||
filesUploadedCntr += 1;
|
||||
if (filesUploadedCntr === filesToUploadCntr) {
|
||||
resolve('done');
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
uploadFiles: uploadFiles
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,57 @@
|
|||
/* global dropdownSelector I18n */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var ChecklistColumnHelper = (function() {
|
||||
function checklistSelect(select, url, values) {
|
||||
var selectedOptions = '';
|
||||
if (values) {
|
||||
$.each(values, function(i, option) {
|
||||
selectedOptions += `<option value="${option.value}">${option.label}</option>`;
|
||||
});
|
||||
}
|
||||
return $(`<select
|
||||
id="${select}"
|
||||
data-placeholder = "Select options..."
|
||||
data-ajax-url = "${url}"
|
||||
data-combine-tags="true"
|
||||
data-select-multiple-all-selected="${I18n.t('libraries.manange_modal_column.checklist_type.all_options')}"
|
||||
data-select-multiple-name="${I18n.t('libraries.manange_modal_column.checklist_type.multiple_options')}"
|
||||
>${selectedOptions}</select>`);
|
||||
}
|
||||
|
||||
function checklistHiddenField(formId, columnId, values) {
|
||||
var idList = [];
|
||||
if (values) {
|
||||
$.each(values, function(i, option) {
|
||||
idList.push(option.value);
|
||||
});
|
||||
} else {
|
||||
idList = '';
|
||||
}
|
||||
return $(`<input form="${formId}"
|
||||
type="hidden"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${JSON.stringify(idList)}"
|
||||
data-type="RepositoryChecklistValue">`);
|
||||
}
|
||||
|
||||
function initialChecklistEditMode(formId, columnId, cell, values) {
|
||||
var select = 'checklist-' + columnId;
|
||||
var checklistUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = checklistSelect(select, checklistUrl, values);
|
||||
var $hiddenField = checklistHiddenField(formId, columnId, values);
|
||||
cell.html($select).append($hiddenField);
|
||||
dropdownSelector.init('#' + select, {
|
||||
noEmptyOption: true,
|
||||
optionClass: 'checkbox-icon',
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
$hiddenField.val(JSON.stringify(dropdownSelector.getValues('#' + select)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialChecklistEditMode: initialChecklistEditMode
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,268 @@
|
|||
/* global Inputmask formatJS */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var DateTimeHelper = (function() {
|
||||
function isValidTimeStr(timeStr) {
|
||||
return /^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/.test(timeStr);
|
||||
}
|
||||
|
||||
function isValidDate(date) {
|
||||
return (date instanceof Date) && !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
function addLeadingZero(value) {
|
||||
return ('0' + value).slice(-2);
|
||||
}
|
||||
|
||||
function recalcTimestamp(date, timeStr) {
|
||||
if (!isValidTimeStr(timeStr)) {
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
return date;
|
||||
}
|
||||
|
||||
date.setHours(timeStr.split(':')[0]);
|
||||
date.setMinutes(timeStr.split(':')[1]);
|
||||
return date;
|
||||
}
|
||||
|
||||
function stringDateTimeFormat(date, format) {
|
||||
let y = date.getFullYear();
|
||||
let m = addLeadingZero(date.getMonth() + 1);
|
||||
let d = addLeadingZero(date.getDate());
|
||||
let hours = addLeadingZero(date.getHours());
|
||||
let mins = addLeadingZero(date.getMinutes());
|
||||
|
||||
if (format === 'dateonly') {
|
||||
return `${y}/${m}/${d}`;
|
||||
}
|
||||
return `${y}/${m}/${d} ${hours}:${mins}`;
|
||||
}
|
||||
|
||||
function insertHiddenField($container) {
|
||||
let formId = $container.data('form-id');
|
||||
let columnId = $container.data('column-id');
|
||||
let dateStr = $container.find('input.date-part').data('selected-date');
|
||||
let timeStr = $container.find('input.time-part').val();
|
||||
let columnType = $container.data('type');
|
||||
let date = new Date(dateStr);
|
||||
let value = '';
|
||||
let hiddenField;
|
||||
|
||||
if (isValidDate(date) && isValidTimeStr(timeStr)) {
|
||||
value = stringDateTimeFormat(recalcTimestamp(date, timeStr), 'full');
|
||||
}
|
||||
|
||||
hiddenField = `
|
||||
<input class="repository-cell-value"
|
||||
type="hidden"
|
||||
form="${formId}"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${value}"
|
||||
data-type="${columnType}"/>`;
|
||||
|
||||
$container.find('input.repository-cell-value').remove();
|
||||
$container.prepend(hiddenField);
|
||||
}
|
||||
|
||||
|
||||
function insertRangeHiddenField($container) {
|
||||
let formId = $container.data('form-id');
|
||||
let columnId = $container.data('column-id');
|
||||
let columnType = $container.data('type');
|
||||
let $startContainer = $container.find('.start-time');
|
||||
let $endContainer = $container.find('.end-time');
|
||||
let startDate = new Date($startContainer.find('input.date-part').data('selected-date'));
|
||||
let startTimeStr = $startContainer.find('input.time-part').val();
|
||||
let endDate = new Date($endContainer.find('input.date-part').data('selected-date'));
|
||||
let endTimeStr = $endContainer.find('input.time-part').val();
|
||||
let hiddenField;
|
||||
let value = '';
|
||||
|
||||
if (isValidDate(startDate)
|
||||
&& isValidTimeStr(startTimeStr)
|
||||
&& isValidDate(endDate)
|
||||
&& isValidTimeStr(endTimeStr)) {
|
||||
let start = stringDateTimeFormat(recalcTimestamp(startDate, startTimeStr), 'full');
|
||||
let end = stringDateTimeFormat(recalcTimestamp(endDate, endTimeStr), 'full');
|
||||
value = JSON.stringify({ start_time: start, end_time: end });
|
||||
}
|
||||
|
||||
hiddenField = `
|
||||
<input class="repository-cell-value"
|
||||
type="hidden"
|
||||
form="${formId}"
|
||||
name="repository_cells[${columnId}]"
|
||||
value='${value}'
|
||||
data-type="${columnType}"/>`;
|
||||
|
||||
$container.find('input.repository-cell-value').remove();
|
||||
$container.prepend(hiddenField);
|
||||
}
|
||||
|
||||
function initChangeEvents($cell) {
|
||||
$cell.find('input.time-part').on('change', function() {
|
||||
let $input = $(this);
|
||||
let $container = $input.closest('.datetime-container');
|
||||
|
||||
if ($container.hasClass('range-type')) {
|
||||
insertRangeHiddenField($container);
|
||||
} else {
|
||||
insertHiddenField($container);
|
||||
}
|
||||
});
|
||||
|
||||
$cell.find('input.date-part').on('dp.change', function(e) {
|
||||
let $input = $(this);
|
||||
let date = e.date._d;
|
||||
let $container = $input.closest('.datetime-container');
|
||||
|
||||
if (date !== undefined) {
|
||||
$input.data('selected-date', stringDateTimeFormat(date, 'dateonly'));
|
||||
} else {
|
||||
$input.data('selected-date', '');
|
||||
}
|
||||
|
||||
if ($container.hasClass('range-type')) {
|
||||
insertRangeHiddenField($container);
|
||||
} else {
|
||||
insertHiddenField($container);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function dateInputField(value, dateDataValue) {
|
||||
return `
|
||||
<input class="form-control editing calendar-input date-part"
|
||||
type="datetime"
|
||||
data-datetime-part="date"
|
||||
data-selected-date="${dateDataValue}"
|
||||
value='${value}'/>
|
||||
`;
|
||||
}
|
||||
|
||||
function timeInputField(value) {
|
||||
return `
|
||||
<input class="form-control editing time-part"
|
||||
type="text"
|
||||
data-mask-type="time"
|
||||
value='${value}'
|
||||
placeholder="HH:mm"/>
|
||||
`;
|
||||
}
|
||||
|
||||
function getDateOrDefault($span, mode) {
|
||||
let dateStr = $span.data('date');
|
||||
let date;
|
||||
if (mode === 'timeonly') {
|
||||
// Set default date if no data in span
|
||||
date = new Date(dateStr);
|
||||
if (isValidDate(date)) {
|
||||
dateStr = stringDateTimeFormat(new Date(date), 'dateonly');
|
||||
} else {
|
||||
dateStr = stringDateTimeFormat(new Date(), 'dateonly');
|
||||
}
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
function getTimeOrDefault($span, mode) {
|
||||
let timeStr = $span.data('time');
|
||||
|
||||
if ((mode === 'dateonly') && (!isValidTimeStr(timeStr))) {
|
||||
timeStr = '00:00';
|
||||
}
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
function initDateTimeEditMode(formId, columnId, $cell, mode, columnType) {
|
||||
let $span = $cell.find('span').first();
|
||||
let date = $span.data('date');
|
||||
let dateDataValue = getDateOrDefault($span, mode);
|
||||
let time = getTimeOrDefault($span, mode);
|
||||
let datetime = $span.data('datetime');
|
||||
let inputFields = `
|
||||
<div class="form-group datetime-container ${mode}"
|
||||
data-form-id="${formId}"
|
||||
data-column-id="${columnId}"
|
||||
data-type="${columnType}"
|
||||
data-current-datetime="${datetime}">
|
||||
${dateInputField(date, dateDataValue)}
|
||||
${timeInputField(time)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
$cell.html(inputFields);
|
||||
|
||||
Inputmask('datetime', {
|
||||
inputFormat: 'HH:MM',
|
||||
placeholder: 'HH:mm',
|
||||
clearIncomplete: true,
|
||||
showMaskOnHover: true,
|
||||
hourFormat: 24
|
||||
}).mask($cell.find('input[data-mask-type="time"]'));
|
||||
|
||||
$cell.find('.calendar-input').datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS });
|
||||
initChangeEvents($cell);
|
||||
}
|
||||
|
||||
function initDateTimeRangeEditMode(formId, columnId, $cell, mode, columnType) {
|
||||
let $startSpan = $cell.find('span').first();
|
||||
let startDate = $startSpan.data('date');
|
||||
let startTime = getTimeOrDefault($startSpan, mode);
|
||||
let startDatetime = $startSpan.data('datetime');
|
||||
let startDateDataValue = getDateOrDefault($startSpan, mode);
|
||||
let $endSpan = $cell.find('span').last();
|
||||
let endDate = $endSpan.data('date');
|
||||
let endTime = getTimeOrDefault($endSpan, mode);
|
||||
let endDatetime = $endSpan.data('datetime');
|
||||
let endDateDataValue = getDateOrDefault($endSpan, mode);
|
||||
|
||||
let inputFields = `
|
||||
<div class="form-group datetime-container range-type"
|
||||
data-form-id="${formId}"
|
||||
data-column-id="${columnId}"
|
||||
data-type="${columnType}"
|
||||
>
|
||||
<div class="start-time ${mode}"
|
||||
data-current-datetime="${startDatetime}">
|
||||
${dateInputField(startDate, startDateDataValue)}
|
||||
${timeInputField(startTime)}
|
||||
</div>
|
||||
<div class="end-time ${mode}"
|
||||
data-current-datetime="${endDatetime}">
|
||||
${dateInputField(endDate, endDateDataValue)}
|
||||
${timeInputField(endTime)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$cell.html(inputFields);
|
||||
|
||||
Inputmask('datetime', {
|
||||
inputFormat: 'HH:MM',
|
||||
placeholder: 'HH:mm',
|
||||
clearIncomplete: true,
|
||||
showMaskOnHover: true,
|
||||
hourFormat: 24
|
||||
}).mask($cell.find('input[data-mask-type="time"]'));
|
||||
|
||||
let $cal1 = $cell.find('.calendar-input').first().datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS });
|
||||
let $cal2 = $cell.find('.calendar-input').last().datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS });
|
||||
|
||||
$cal1.on('dp.change', function(e) {
|
||||
$cal2.data('DateTimePicker').minDate(e.date);
|
||||
});
|
||||
$cal2.on('dp.change', function(e) {
|
||||
$cal1.data('DateTimePicker').maxDate(e.date);
|
||||
});
|
||||
|
||||
initChangeEvents($cell);
|
||||
}
|
||||
|
||||
return {
|
||||
initDateTimeEditMode: initDateTimeEditMode,
|
||||
initDateTimeRangeEditMode: initDateTimeRangeEditMode
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,45 @@
|
|||
/* global dropdownSelector */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var ListColumnHelper = (function() {
|
||||
function listSelect(select, url, value) {
|
||||
var selectedOption = '';
|
||||
if (value && value.value) {
|
||||
selectedOption = `<option value="${value.value}">${value.label}</option>`;
|
||||
}
|
||||
return $(`<select
|
||||
id="${select}"
|
||||
data-placeholder = "Select option..."
|
||||
data-ajax-url = "${url}"
|
||||
>${selectedOption}</select>`);
|
||||
}
|
||||
|
||||
function listHiddenField(formId, columnId, value) {
|
||||
var originalValue = value ? value.value : '';
|
||||
return $(`<input form="${formId}"
|
||||
type="hidden"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${originalValue}"
|
||||
data-type="RepositoryListValue">`);
|
||||
}
|
||||
|
||||
function initialListEditMode(formId, columnId, cell, value = null) {
|
||||
var select = 'list-' + columnId;
|
||||
var listUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = listSelect(select, listUrl, value);
|
||||
var $hiddenField = listHiddenField(formId, columnId, value);
|
||||
cell.html($select).append($hiddenField);
|
||||
dropdownSelector.init('#' + select, {
|
||||
singleSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
var values = dropdownSelector.getValues('#' + select);
|
||||
$hiddenField.val(values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialListEditMode: initialListEditMode
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,46 @@
|
|||
/* global dropdownSelector */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var StatusColumnHelper = (function() {
|
||||
function statusSelect(select, url, value) {
|
||||
var selectedOption = '';
|
||||
if (value && value.value) {
|
||||
selectedOption = `<option value="${value.value}">${value.label}</option>`;
|
||||
}
|
||||
|
||||
return $(`<select
|
||||
id="${select}"
|
||||
data-placeholder = "Select option..."
|
||||
data-ajax-url = "${url}"
|
||||
>${selectedOption}</select>`);
|
||||
}
|
||||
|
||||
function statusHiddenField(formId, columnId, value) {
|
||||
var originalValue = value ? value.value : '';
|
||||
return $(`<input form="${formId}"
|
||||
type="hidden"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${originalValue}"
|
||||
data-type="RepositoryStatusValue">`);
|
||||
}
|
||||
|
||||
function initialStatusEditMode(formId, columnId, cell, value = null) {
|
||||
var select = 'status-list-' + columnId;
|
||||
var listUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = statusSelect(select, listUrl, value);
|
||||
var $hiddenField = statusHiddenField(formId, columnId, value);
|
||||
cell.html($select).append($hiddenField);
|
||||
dropdownSelector.init('#' + select, {
|
||||
singleSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
var values = dropdownSelector.getValues('#' + select);
|
||||
$hiddenField.val(values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialStatusEditMode: initialStatusEditMode
|
||||
};
|
||||
}());
|
149
app/assets/javascripts/repositories/renderers/edit_renderers.js
Normal file
149
app/assets/javascripts/repositories/renderers/edit_renderers.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
global ListColumnHelper ChecklistColumnHelper StatusColumnHelper SmartAnnotation I18n
|
||||
GLOBAL_CONSTANTS DateTimeHelper
|
||||
*/
|
||||
|
||||
$.fn.dataTable.render.editRowName = function(formId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
let text = $cell.find('a').first().text();
|
||||
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="text"
|
||||
name="repository_row[name]"
|
||||
value="${text}"
|
||||
data-type="RowName">
|
||||
</div>
|
||||
`);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryAssetValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
let empty = $cell.is(':empty');
|
||||
let fileName = $cell.find('a.file-preview-link').text();
|
||||
|
||||
$cell.html(`
|
||||
<div class="file-editing">
|
||||
<div class="file-hidden-field-container hidden"></div>
|
||||
<input class=""
|
||||
id="repository_file_${columnId}"
|
||||
form="${formId}"
|
||||
type="file"
|
||||
data-col-id="${columnId}"
|
||||
data-is-empty="${empty}"
|
||||
value=""
|
||||
data-type="RepositoryAssetValue">
|
||||
<div class="file-upload-button ${empty ? 'new-file' : ''}">
|
||||
<label for="repository_file_${columnId}">${I18n.t('repositories.table.assets.select_file_btn', { max_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB })}</label>
|
||||
<span class="icon"><i class="fas fa-paperclip"></i></span><span class="label-asset">${fileName}</span>
|
||||
<span class="delete-action fas fa-trash"> </span>
|
||||
</div>
|
||||
</div>`);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryTextValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
let text = $cell.text();
|
||||
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="text"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${text}"
|
||||
data-type="RepositoryTextValue">
|
||||
</div>`);
|
||||
|
||||
SmartAnnotation.init($cell.find('input'));
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryListValue = function(formId, columnId, cell) {
|
||||
var $cell = $(cell.node());
|
||||
var currentElement = $cell.find('.list-label');
|
||||
var currentValue = null;
|
||||
if (currentElement.length) {
|
||||
currentValue = {
|
||||
value: currentElement.attr('data-value-id'),
|
||||
label: currentElement.text()
|
||||
};
|
||||
}
|
||||
|
||||
ListColumnHelper.initialListEditMode(formId, columnId, $cell, currentValue);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryStatusValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
var currentElement = $cell.find('.status-label');
|
||||
var iconElement = $cell.find('.repository-status-value-icon');
|
||||
var currentValue = null;
|
||||
if (currentElement.length) {
|
||||
currentValue = {
|
||||
value: currentElement.attr('data-value-id'),
|
||||
label: iconElement.text() + ' ' + currentElement.text()
|
||||
};
|
||||
}
|
||||
|
||||
StatusColumnHelper.initialStatusEditMode(formId, columnId, $cell, currentValue);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryDateTimeValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, '', 'RepositoryDateTimeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryDateValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, 'dateonly', 'RepositoryDateValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryTimeValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, 'timeonly', 'RepositoryTimeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryDateTimeRangeValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, '', 'RepositoryDateTimeRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryDateRangeValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, 'dateonly', 'RepositoryDateRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryTimeRangeValue = function(formId, columnId, cell) {
|
||||
let $cell = $(cell.node());
|
||||
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, 'timeonly', 'RepositoryTimeRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryChecklistValue = function(formId, columnId, cell) {
|
||||
var $cell = $(cell.node());
|
||||
var currentValue = $cell.find('.checklist-options').data('checklist-items');
|
||||
ChecklistColumnHelper.initialChecklistEditMode(formId, columnId, $cell, currentValue);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryNumberValue = function(formId, columnId, cell, $header) {
|
||||
let $cell = $(cell.node());
|
||||
let decimals = Number($header.data('metadata-decimals'));
|
||||
let number = parseFloat(Number($cell.text()).toFixed(decimals));
|
||||
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="number"
|
||||
name="repository_cells[${columnId}]"
|
||||
value="${number}"
|
||||
onchange="if (this.value !== '') { this.value = parseFloat(Number(this.value).toFixed(${decimals})); }"
|
||||
data-type="RepositoryNumberValue">
|
||||
</div>`);
|
||||
};
|
108
app/assets/javascripts/repositories/renderers/new_renderers.js
Normal file
108
app/assets/javascripts/repositories/renderers/new_renderers.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
global ListColumnHelper ChecklistColumnHelper StatusColumnHelper SmartAnnotation I18n
|
||||
GLOBAL_CONSTANTS DateTimeHelper
|
||||
*/
|
||||
|
||||
$.fn.dataTable.render.newRowName = function(formId, $cell) {
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="text"
|
||||
name="repository_row[name]"
|
||||
value=""
|
||||
data-type="RowName">
|
||||
</div>
|
||||
`);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryAssetValue = function(formId, columnId, $cell) {
|
||||
$cell.html(`
|
||||
<div class="file-editing">
|
||||
<div class="file-hidden-field-container hidden"></div>
|
||||
<input class=""
|
||||
id="repository_file_${columnId}"
|
||||
form="${formId}"
|
||||
type="file"
|
||||
data-col-id="${columnId}"
|
||||
data-is-empty="true"
|
||||
value=""
|
||||
data-type="RepositoryAssetValue">
|
||||
<div class="file-upload-button new-file">
|
||||
<label for="repository_file_${columnId}">${I18n.t('repositories.table.assets.select_file_btn', { max_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB })}</label>
|
||||
<span class="icon"><i class="fas fa-paperclip"></i></span><span class="label-asset"></span>
|
||||
<span class="delete-action fas fa-trash"> </span>
|
||||
</div>
|
||||
</div>`);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryTextValue = function(formId, columnId, $cell) {
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="text"
|
||||
name="repository_cells[${columnId}]"
|
||||
value=""
|
||||
data-type="RepositoryTextValue">
|
||||
</div>`);
|
||||
|
||||
SmartAnnotation.init($cell.find('input'));
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryListValue = function(formId, columnId, $cell) {
|
||||
ListColumnHelper.initialListEditMode(formId, columnId, $cell);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryStatusValue = function(formId, columnId, $cell) {
|
||||
StatusColumnHelper.initialStatusEditMode(formId, columnId, $cell);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryChecklistValue = function(formId, columnId, $cell) {
|
||||
ChecklistColumnHelper.initialChecklistEditMode(formId, columnId, $cell);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryNumberValue = function(formId, columnId, $cell, $header) {
|
||||
let decimals = Number($header.data('metadata-decimals'));
|
||||
|
||||
$cell.html(`
|
||||
<div class="form-group">
|
||||
<input class="form-control editing"
|
||||
form="${formId}"
|
||||
type="number"
|
||||
name="repository_cells[${columnId}]"
|
||||
value=""
|
||||
onchange="if (this.value !== '') { this.value = parseFloat(Number(this.value).toFixed(${decimals})); }"
|
||||
data-type="RepositoryNumberValue">
|
||||
</div>`);
|
||||
|
||||
SmartAnnotation.init($cell.find('input'));
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryDateTimeValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, '', 'RepositoryDateTimeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryTimeValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, 'timeonly', 'RepositoryTimeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryDateValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeEditMode(formId, columnId, $cell, 'dateonly', 'RepositoryDateValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryDateTimeRangeValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, '', 'RepositoryDateTimeRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryDateRangeValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, 'dateonly', 'RepositoryDateRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryTimeRangeValue = function(formId, columnId, $cell) {
|
||||
DateTimeHelper.initDateTimeRangeEditMode(formId, columnId, $cell, 'timeonly', 'RepositoryTimeRangeValue');
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.newRepositoryCheckboxValue = function(formId, columnId) {
|
||||
return '';
|
||||
};
|
150
app/assets/javascripts/repositories/renderers/view_renderers.js
Normal file
150
app/assets/javascripts/repositories/renderers/view_renderers.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
/* global I18n */
|
||||
|
||||
$.fn.dataTable.render.RepositoryAssetValue = function(data) {
|
||||
var asset = data.value;
|
||||
return `
|
||||
<div class="asset-value-cell">
|
||||
${asset.icon_html}
|
||||
<a class="file-preview-link"
|
||||
id="modal_link${asset.id}"
|
||||
data-no-turbolink="true"
|
||||
data-id="true"
|
||||
data-status="asset-present"
|
||||
data-preview-url="${asset.preview_url}"
|
||||
href="${asset.url}"
|
||||
>
|
||||
${asset.file_name}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryAssetValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTextValue = function(data) {
|
||||
return data.value;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryTextValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryListValue = function(data) {
|
||||
return `<span data-value-id="${data.value.id}" class="list-label">${data.value.text}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryListValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryStatusValue = function(data) {
|
||||
return `
|
||||
<span class="repository-status-value-icon">${data.value.icon}</span>
|
||||
<span data-value-id="${data.value.id}" class="status-label">${data.value.status}</span>
|
||||
`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryStatusValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryDateValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateValue = function(data) {
|
||||
return `<span data-datetime="${data.value.datetime}" data-date="${data.value.formatted}">${data.value.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryDateTimeValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateTimeValue = function(data) {
|
||||
return `<span data-time="${data.value.time_formatted}"
|
||||
data-datetime="${data.value.datetime}"
|
||||
data-date="${data.value.date_formatted}">${data.value.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryTimeValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTimeValue = function(data) {
|
||||
return `<span data-time="${data.value.formatted}"
|
||||
data-datetime="${data.value.datetime}">${data.value.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryTimeRangeValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTimeRangeValue = function(data) {
|
||||
return `<span data-time="${data.value.start_time.formatted}"
|
||||
data-datetime="${data.value.start_time.datetime}">${data.value.start_time.formatted}</span> -
|
||||
<span data-time="${data.value.end_time.formatted}"
|
||||
data-datetime="${data.value.end_time.datetime}">${data.value.end_time.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryDateTimeRangeValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateTimeRangeValue = function(data) {
|
||||
return `<span data-time="${data.value.start_time.time_formatted}"
|
||||
data-datetime="${data.value.start_time.datetime}"
|
||||
data-date="${data.value.start_time.date_formatted}">${data.value.start_time.formatted}</span> -
|
||||
<span data-time="${data.value.end_time.time_formatted}"
|
||||
data-datetime="${data.value.end_time.datetime}"
|
||||
data-date="${data.value.end_time.date_formatted}">${data.value.end_time.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryDateRangeValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateRangeValue = function(data) {
|
||||
return `<span data-datetime="${data.value.start_time.datetime}"
|
||||
data-date="${data.value.start_time.formatted}">${data.value.start_time.formatted}</span> -
|
||||
<span data-datetime="${data.value.end_time.datetime}"
|
||||
data-date="${data.value.end_time.formatted}">${data.value.end_time.formatted}</span>`;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryChecklistValue = function(data) {
|
||||
var render = '—';
|
||||
var options = data.value;
|
||||
var optionsList;
|
||||
if (options.length === 1) {
|
||||
render = `<span class="checklist-options" data-checklist-items='${JSON.stringify(options)}'>
|
||||
${options[0].label}
|
||||
</span>`;
|
||||
} else if (options.length > 1) {
|
||||
optionsList = $(' <ul class="dropdown-menu checklist-dropdown-menu" role="menu"></ul');
|
||||
$.each(options, function(i, option) {
|
||||
$(`<li class="checklist-item">${option.label}</li>`).appendTo(optionsList);
|
||||
});
|
||||
|
||||
render = `
|
||||
<span class="dropdown checklist-dropdown">
|
||||
<span data-toggle="dropdown" class="checklist-options" aria-haspopup="true" data-checklist-items='${JSON.stringify(options)}'>
|
||||
${options.length} ${I18n.t('libraries.manange_modal_column.checklist_type.multiple_options')}
|
||||
</span>
|
||||
${optionsList[0].outerHTML}
|
||||
</span>`;
|
||||
}
|
||||
return render;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryChecklistValue = function() {
|
||||
return '—';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.defaultRepositoryNumberValue = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryNumberValue = function(data) {
|
||||
return parseFloat(Number(data.value).toFixed(data.value_decimals));
|
||||
};
|
File diff suppressed because it is too large
Load diff
207
app/assets/javascripts/repositories/row_editor.js
Normal file
207
app/assets/javascripts/repositories/row_editor.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
globals HelperModule animateSpinner SmartAnnotation Asset
|
||||
*/
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var RepositoryDatatableRowEditor = (function() {
|
||||
const NAME_COLUMN_ID = 'row-name';
|
||||
const TABLE_ROW = '<tr></tr>';
|
||||
const TABLE_CELL = '<td></td>';
|
||||
|
||||
var TABLE;
|
||||
|
||||
// Initialize SmartAnnotation
|
||||
function initSmartAnnotation($row) {
|
||||
$row.find('[data-object="repository_cell"]').each(function(el) {
|
||||
if (el.data('atwho')) {
|
||||
SmartAnnotation.init(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateAndSubmit($table) {
|
||||
let $form = $table.find('.repository-row-edit-form');
|
||||
let $row = $form.closest('tr');
|
||||
let valid = true;
|
||||
let directUrl = $table.data('direct-upload-url');
|
||||
let $files = $row.find('input[type=file]');
|
||||
$row.find('.has-error').removeClass('has-error').find('span').remove();
|
||||
|
||||
// Validations here
|
||||
$row.find('input').each(function() {
|
||||
let dataType = $(this).data('type');
|
||||
if (!dataType) return;
|
||||
|
||||
valid = $.fn.dataTable.render[dataType + 'Validator']($(this));
|
||||
if (!valid) return false;
|
||||
});
|
||||
|
||||
if (!valid) return false;
|
||||
|
||||
// DirectUpload here
|
||||
let uploadPromise = Asset.uploadFiles($files, directUrl);
|
||||
|
||||
// Submission here
|
||||
uploadPromise
|
||||
.then(function() {
|
||||
animateSpinner(null, true);
|
||||
$form.submit();
|
||||
return false;
|
||||
}).catch((reason) => {
|
||||
alert(reason);
|
||||
return false;
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function initAssetCellActions($row) {
|
||||
let fileInputs = $row.find('input[type=file]');
|
||||
let deleteButtons = $row.find('.file-upload-button>span.delete-action');
|
||||
|
||||
fileInputs.on('change', function() {
|
||||
let $input = $(this);
|
||||
let $fileBtn = $input.next('.file-upload-button');
|
||||
let $label = $fileBtn.find('.label-asset');
|
||||
|
||||
$label.text($input[0].files[0].name);
|
||||
$fileBtn.removeClass('new-file');
|
||||
});
|
||||
|
||||
|
||||
deleteButtons.on('click', function() {
|
||||
let $fileBtn = $(this).parent();
|
||||
let $input = $fileBtn.prev('input[type=file]');
|
||||
let $label = $fileBtn.find('.label-asset');
|
||||
|
||||
$fileBtn.addClass('new-file');
|
||||
$label.text('');
|
||||
$input.val('');
|
||||
|
||||
if (!$input.data('is-empty')) { // set hidden field for deletion only if original value has been set on rendering
|
||||
$input
|
||||
.prev('.file-hidden-field-container')
|
||||
.html(`<input type="hidden"
|
||||
form="${$input.attr('form')}"
|
||||
name="repository_cells[${$input.data('col-id')}]"
|
||||
value=""/>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initFormSubmitAction(table) {
|
||||
TABLE = table;
|
||||
let $table = $(TABLE.table().node());
|
||||
|
||||
$table.on('ajax:success', '.repository-row-edit-form', function(ev, data) {
|
||||
TABLE.ajax.reload();
|
||||
HelperModule.flashAlertMsg(data.flash, 'success');
|
||||
});
|
||||
|
||||
$table.on('ajax:error', '.repository-row-edit-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
});
|
||||
|
||||
$table.on('ajax:complete', '.repository-row-edit-form', function() {
|
||||
animateSpinner(null, false);
|
||||
});
|
||||
}
|
||||
|
||||
function addNewRow(table) {
|
||||
TABLE = table;
|
||||
|
||||
let $row = $(TABLE_ROW);
|
||||
const formId = 'repositoryNewRowForm';
|
||||
let actionUrl = $(TABLE.table().node()).data('createRecord');
|
||||
let rowForm = $(`
|
||||
<td>
|
||||
<form id="${formId}"
|
||||
class="repository-row-edit-form"
|
||||
action="${actionUrl}"
|
||||
method="post"
|
||||
data-remote="true">
|
||||
</form>
|
||||
</td>
|
||||
`);
|
||||
|
||||
// First two columns are always present and visible
|
||||
$row.append(rowForm);
|
||||
$row.append($(TABLE_CELL));
|
||||
|
||||
$(TABLE.table().node()).find('tbody').prepend($row);
|
||||
|
||||
table.columns().every(function() {
|
||||
let column = this;
|
||||
let $header = $(column.header());
|
||||
|
||||
if (column.index() < 2) return;
|
||||
if (!column.visible()) return;
|
||||
|
||||
let columnId = $header.attr('id');
|
||||
|
||||
let $cell = $(TABLE_CELL).appendTo($row);
|
||||
|
||||
if (columnId === NAME_COLUMN_ID) {
|
||||
$.fn.dataTable.render.newRowName(formId, $cell);
|
||||
} else {
|
||||
let dataType = $header.data('type');
|
||||
if (dataType) {
|
||||
$.fn.dataTable.render['new' + dataType](formId, columnId, $cell, $header);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initSmartAnnotation($row);
|
||||
|
||||
TABLE.columns.adjust();
|
||||
}
|
||||
|
||||
function switchRowToEditMode(row) {
|
||||
let $row = $(row.node());
|
||||
let itemId = row.id();
|
||||
let formId = `repositoryRowForm${itemId}`;
|
||||
let requestUrl = $(TABLE.table().node()).data('current-uri');
|
||||
let rowForm = $(`
|
||||
<form id="${formId}"
|
||||
class="repository-row-edit-form"
|
||||
action="${row.data().recordUpdateUrl}"
|
||||
method="patch"
|
||||
data-remote="true"
|
||||
data-row-id="${itemId}">
|
||||
<input name="id" type="hidden" value="${itemId}" />
|
||||
<input name="request_url" type="hidden" value="${requestUrl}" />
|
||||
</form>
|
||||
`);
|
||||
|
||||
$row.find('td').first().append(rowForm);
|
||||
|
||||
TABLE.cells(row.index(), row.columns().eq(0)).every(function() {
|
||||
let $header = $(TABLE.columns(this.index().column).header());
|
||||
let columnId = $header.attr('id');
|
||||
let dataType = $header.data('type');
|
||||
let cell = this;
|
||||
|
||||
if (!cell.column(cell.index().column).visible()) return true; // return if cell is not visible
|
||||
|
||||
if (columnId === NAME_COLUMN_ID) {
|
||||
$.fn.dataTable.render.editRowName(formId, cell);
|
||||
} else if (dataType) {
|
||||
$.fn.dataTable.render['edit' + dataType](formId, columnId, cell, $header);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
initSmartAnnotation($row);
|
||||
initAssetCellActions($row);
|
||||
|
||||
TABLE.columns.adjust();
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
initFormSubmitAction: initFormSubmitAction,
|
||||
validateAndSubmit: validateAndSubmit,
|
||||
switchRowToEditMode: switchRowToEditMode,
|
||||
addNewRow: addNewRow
|
||||
});
|
||||
}());
|
152
app/assets/javascripts/repositories/validators/base_validator.js
Normal file
152
app/assets/javascripts/repositories/validators/base_validator.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* global GLOBAL_CONSTANTS textValidator I18n */
|
||||
|
||||
$.fn.dataTable.render.RowNameValidator = function($input) {
|
||||
return textValidator(undefined, $input, 1, GLOBAL_CONSTANTS.NAME_MAX_LENGTH);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTextValueValidator = function($input) {
|
||||
return textValidator(undefined, $input, 1, GLOBAL_CONSTANTS.NAME_MAX_LENGTH);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryListValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryStatusValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryAssetValueValidator = function($input) {
|
||||
let file = $input[0].files[0];
|
||||
if (!file) return true;
|
||||
|
||||
let valid = (file.size < GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB * 1024 * 1024);
|
||||
if (valid) return true;
|
||||
|
||||
let errorMessage = I18n.t('general.file.size_exceeded', { file_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB });
|
||||
let $btn = $input.next('.file-upload-button');
|
||||
$btn.addClass('error');
|
||||
$btn.attr('data-error-text', errorMessage);
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryChecklistValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryNumberValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateTimeValueValidator = function($input) {
|
||||
let $container = $input.parents('.datetime-container');
|
||||
let $date = $container.find('input.date-part');
|
||||
let $time = $container.find('input.time-part');
|
||||
|
||||
if (($date.val() === '') === ($time.val() === '')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$container.addClass('has-error');
|
||||
$container.append('<span class="help-block">Set both or none</span>');
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTimeValueValidator = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateTimeRangeValueValidator = function($input) {
|
||||
let $container = $input.parents('.datetime-container');
|
||||
let $dateS = $container.find('.start-time input.date-part');
|
||||
let $timeS = $container.find('.start-time input.time-part');
|
||||
let $dateE = $container.find('.end-time input.date-part');
|
||||
let $timeE = $container.find('.end-time input.time-part');
|
||||
let isValid = true;
|
||||
let errorMessage;
|
||||
let startTime;
|
||||
let endTime;
|
||||
let a = [];
|
||||
|
||||
if ($input.val()) {
|
||||
startTime = new Date(JSON.parse($input.val()).start_time);
|
||||
endTime = new Date(JSON.parse($input.val()).end_time);
|
||||
}
|
||||
|
||||
a.push($dateS.val() === '');
|
||||
a.push($timeS.val() === '');
|
||||
a.push($dateE.val() === '');
|
||||
a.push($timeE.val() === '');
|
||||
|
||||
if (a.filter((v, i, arr) => arr.indexOf(v) === i).length > 1) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.set_all_or_none');
|
||||
} else if (($input.val()) && (startTime > endTime)) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.not_valid_range');
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$container.addClass('has-error');
|
||||
$container.append(`<span class="help-block">${errorMessage}</span>`);
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryDateRangeValueValidator = function($input) {
|
||||
let $container = $input.parents('.datetime-container');
|
||||
let $dateS = $container.find('.start-time input.date-part');
|
||||
let $dateE = $container.find('.end-time input.date-part');
|
||||
let isValid = true;
|
||||
let errorMessage;
|
||||
let endTime;
|
||||
let startTime;
|
||||
if ($input.val()) {
|
||||
startTime = new Date(JSON.parse($input.val()).start_time);
|
||||
endTime = new Date(JSON.parse($input.val()).end_time);
|
||||
}
|
||||
|
||||
if (($dateS.val() === '') !== ($dateE.val() === '')) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.set_all_or_none');
|
||||
} else if (($input.val()) && (startTime > endTime)) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.not_valid_range');
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$container.addClass('has-error');
|
||||
$container.append(`<span class="help-block">${errorMessage}</span>`);
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryTimeRangeValueValidator = function($input) {
|
||||
let $container = $input.parents('.datetime-container');
|
||||
let $timeS = $container.find('.start-time input.time-part');
|
||||
let $timeE = $container.find('.end-time input.time-part');
|
||||
let isValid = true;
|
||||
let errorMessage;
|
||||
|
||||
if (($timeS.val() === '') !== ($timeE.val() === '')) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.set_all_or_none');
|
||||
} else if ($timeS.val() > $timeE.val()) {
|
||||
isValid = false;
|
||||
errorMessage = I18n.t('repositories.table.date_time.errors.not_valid_range');
|
||||
}
|
||||
if (isValid) {
|
||||
return true;
|
||||
}
|
||||
$container.addClass('has-error');
|
||||
$container.append(`<span class="help-block">${errorMessage}</span>`);
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,76 @@
|
|||
/* global GLOBAL_CONSTANTS dropdownSelector RepositoryListColumnType */
|
||||
|
||||
var RepositoryChecklistColumnType = (function() {
|
||||
var manageModal = '#manage-repository-column';
|
||||
var delimiterDropdown = '.checklist-column-type .delimiter';
|
||||
var itemsTextarea = '.checklist-column-type .items-textarea';
|
||||
var previewContainer = '.checklist-column-type .dropdown-preview';
|
||||
var dropdownOptions = '.checklist-column-type .dropdown-options';
|
||||
|
||||
function initChecklistDropdown() {
|
||||
dropdownSelector.init(previewContainer + ' .preview-select', {
|
||||
noEmptyOption: true,
|
||||
optionClass: 'checkbox-icon',
|
||||
selectAppearance: 'simple'
|
||||
});
|
||||
}
|
||||
|
||||
function initDropdownItemsTextArea() {
|
||||
var $manageModal = $(manageModal);
|
||||
var columnNameInput = '#repository-column-name';
|
||||
|
||||
|
||||
$manageModal
|
||||
.on('show.bs.modal', function() {
|
||||
setTimeout(() => { initChecklistDropdown(); }, 200);
|
||||
})
|
||||
.on('change keyup paste', itemsTextarea, function() {
|
||||
RepositoryListColumnType.refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
initChecklistDropdown();
|
||||
})
|
||||
.on('change', delimiterDropdown, function() {
|
||||
RepositoryListColumnType.refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
initChecklistDropdown();
|
||||
})
|
||||
.on('columnModal::partialLoadedForRepositoryChecklistValue', function() {
|
||||
RepositoryListColumnType.refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
initChecklistDropdown();
|
||||
})
|
||||
.on('keyup change', columnNameInput, function() {
|
||||
$manageModal.find(previewContainer).find('.preview-label').html($manageModal.find(columnNameInput).val());
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
initDropdownItemsTextArea();
|
||||
},
|
||||
checkValidation: () => {
|
||||
var $manageModal = $(manageModal);
|
||||
var count = $manageModal.find(previewContainer).find('.items-count').attr('data-count');
|
||||
return count < GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN;
|
||||
},
|
||||
loadParams: () => {
|
||||
var repositoryColumnParams = {};
|
||||
var options = JSON.parse($(dropdownOptions).val());
|
||||
repositoryColumnParams.repository_checklist_items_attributes = options;
|
||||
repositoryColumnParams.metadata = { delimiter: $(delimiterDropdown).data('used-delimiter') };
|
||||
return repositoryColumnParams;
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,17 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryDateColumnType = (function() {
|
||||
return {
|
||||
init: () => {},
|
||||
checkValidation: () => {
|
||||
return true;
|
||||
},
|
||||
loadParams: () => {
|
||||
var isRange = $('#date-range').is(':checked');
|
||||
var columnType = $('#repository-column-data-type').val();
|
||||
if (isRange) {
|
||||
columnType = columnType.replace('Value', 'RangeValue');
|
||||
}
|
||||
return { column_type: columnType };
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,17 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryDateTimeColumnType = (function() {
|
||||
return {
|
||||
init: () => {},
|
||||
checkValidation: () => {
|
||||
return true;
|
||||
},
|
||||
loadParams: () => {
|
||||
var isRange = $('#datetime-range').is(':checked');
|
||||
var columnType = $('#repository-column-data-type').val();
|
||||
if (isRange) {
|
||||
columnType = columnType.replace('Value', 'RangeValue');
|
||||
}
|
||||
return { column_type: columnType };
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,156 @@
|
|||
/* global GLOBAL_CONSTANTS */
|
||||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryListColumnType = (function() {
|
||||
var manageModal = '#manage-repository-column';
|
||||
var delimiterDropdown = '.list-column-type #delimiter';
|
||||
var itemsTextarea = '.list-column-type #items-textarea';
|
||||
var previewContainer = '.list-column-type .dropdown-preview';
|
||||
var dropdownOptions = '.list-column-type #dropdown-options';
|
||||
|
||||
function onlyUnique(value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
function textToItems(text, delimiterContainer) {
|
||||
var delimiter = $(delimiterContainer).val();
|
||||
var res = [];
|
||||
var usedDelimiter = '';
|
||||
var definedDelimiters = {
|
||||
return: '\n',
|
||||
comma: ',',
|
||||
semicolon: ';',
|
||||
space: ' '
|
||||
};
|
||||
|
||||
var delimiters = [];
|
||||
if (delimiter === 'auto') {
|
||||
delimiters = ['\n', ',', ';', ' '];
|
||||
} else {
|
||||
delimiters.push(definedDelimiters[delimiter]);
|
||||
}
|
||||
|
||||
$.each(delimiters, (index, currentDelimiter) => {
|
||||
res = text.trim().split(currentDelimiter);
|
||||
usedDelimiter = Object
|
||||
.keys(definedDelimiters)
|
||||
.find(key => definedDelimiters[key] === currentDelimiter);
|
||||
|
||||
if (res.length > 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
res = res.filter(Boolean).filter(onlyUnique);
|
||||
|
||||
$.each(res, (index, option) => {
|
||||
res[index] = option.slice(0, GLOBAL_CONSTANTS.NAME_MAX_LENGTH);
|
||||
});
|
||||
|
||||
$(delimiterContainer).attr('data-used-delimiter', usedDelimiter);
|
||||
return res;
|
||||
}
|
||||
|
||||
function pluralizeWord(count, noun, suffix = 's') {
|
||||
return `${noun}${count !== 1 ? suffix : ''}`;
|
||||
}
|
||||
|
||||
|
||||
function drawDropdownPreview(items, container) {
|
||||
var $manageModal = $(manageModal);
|
||||
var $dropdownPreview = $manageModal.find(container).find('.preview-select');
|
||||
$('option', $dropdownPreview).remove();
|
||||
$.each(items, function(i, item) {
|
||||
$dropdownPreview.append($('<option>', {
|
||||
value: item,
|
||||
text: item
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCounter(number, container) {
|
||||
var $manageModal = $(manageModal);
|
||||
var $counterContainer = $manageModal.find(container).find('.limit-counter-container');
|
||||
var $btn = $manageModal.find('.column-save-btn');
|
||||
|
||||
$counterContainer.find('.items-count').html(number).attr('data-count', number);
|
||||
|
||||
if (number >= GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN) {
|
||||
$counterContainer.addClass('error-to-many-items');
|
||||
$btn.addClass('disabled');
|
||||
} else {
|
||||
$counterContainer.removeClass('error-to-many-items');
|
||||
$btn.removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function refreshPreviewDropdownList(preview, textarea, delimiterContainer, dropdown) {
|
||||
var items = textToItems($(textarea).val(), delimiterContainer);
|
||||
var hashItems = [];
|
||||
drawDropdownPreview(items, preview);
|
||||
refreshCounter(items.length, preview);
|
||||
|
||||
$.each(items, (index, option) => {
|
||||
hashItems.push({ data: option });
|
||||
});
|
||||
|
||||
$(dropdown).val(JSON.stringify(hashItems));
|
||||
$(preview).find('.items-label').html(pluralizeWord(items.length, 'item'));
|
||||
}
|
||||
|
||||
function initDropdownItemsTextArea() {
|
||||
var $manageModal = $(manageModal);
|
||||
var columnNameInput = '#repository-column-name';
|
||||
|
||||
$manageModal
|
||||
.on('change keyup paste', itemsTextarea, function() {
|
||||
refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
})
|
||||
.on('change', delimiterDropdown, function() {
|
||||
refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
})
|
||||
.on('columnModal::partialLoadedForRepositoryListValue', function() {
|
||||
refreshPreviewDropdownList(
|
||||
previewContainer,
|
||||
itemsTextarea,
|
||||
delimiterDropdown,
|
||||
dropdownOptions
|
||||
);
|
||||
})
|
||||
.on('keyup change', columnNameInput, function() {
|
||||
$manageModal.find(previewContainer).find('.preview-label').html($manageModal.find(columnNameInput).val());
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
initDropdownItemsTextArea();
|
||||
},
|
||||
checkValidation: () => {
|
||||
var $manageModal = $(manageModal);
|
||||
var count = $manageModal.find(previewContainer).find('.items-count').attr('data-count');
|
||||
return count < GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN;
|
||||
},
|
||||
loadParams: () => {
|
||||
var repositoryColumnParams = {};
|
||||
var options = JSON.parse($(dropdownOptions).val());
|
||||
repositoryColumnParams.repository_list_items_attributes = options;
|
||||
repositoryColumnParams.metadata = { delimiter: $(delimiterDropdown).data('used-delimiter') };
|
||||
return repositoryColumnParams;
|
||||
},
|
||||
|
||||
refreshPreviewDropdownList: (preview, textarea, delimiter, dropdown) => {
|
||||
refreshPreviewDropdownList(preview, textarea, delimiter, dropdown);
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,13 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryNumberColumnType = (function() {
|
||||
return {
|
||||
init: () => {},
|
||||
checkValidation: () => {
|
||||
return true;
|
||||
},
|
||||
loadParams: () => {
|
||||
var decimals = $('#decimals').val();
|
||||
return { metadata: { decimals: decimals } };
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,169 @@
|
|||
/* global GLOBAL_CONSTANTS I18n */
|
||||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryStatusColumnType = (function() {
|
||||
var manageModal = '#manage-repository-column';
|
||||
|
||||
function statusTemplate() {
|
||||
return `
|
||||
<div class="status-item-container loading">
|
||||
<div class="status-item-icon"></div>
|
||||
<input placeholder=${I18n.t('libraries.manange_modal_column.name_placeholder')}
|
||||
class="status-item-field"
|
||||
type="text"/>
|
||||
<span class="status-item-icon-trash fas fa-trash"></span>
|
||||
</div>
|
||||
<div class="emojis-picker">
|
||||
<span data-emoji-code="😜">😜</span>
|
||||
<span data-emoji-code="😈">😈</span>
|
||||
<span data-emoji-code="😎">😎</span>
|
||||
<span data-emoji-code="😓">😓</span>
|
||||
<span data-emoji-code="😗">😗</span>
|
||||
<span data-emoji-code="😘">😘</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
var $manageModal = $(manageModal);
|
||||
var $statusRows = $manageModal.find('.status-item-container:not([data-removed])');
|
||||
var $btn = $manageModal.find('.column-save-btn');
|
||||
|
||||
$.each($statusRows, (index, statusRow) => {
|
||||
var $row = $(statusRow);
|
||||
var $statusField = $row.find('.status-item-field');
|
||||
var $icon = $row.find('.status-item-icon');
|
||||
var stringLength = $statusField.val().length;
|
||||
|
||||
if (stringLength < GLOBAL_CONSTANTS.NAME_MIN_LENGTH
|
||||
|| stringLength > GLOBAL_CONSTANTS.NAME_MAX_LENGTH
|
||||
|| !$icon.attr('data-icon')) {
|
||||
$row.addClass('error');
|
||||
} else {
|
||||
$row.removeClass('error');
|
||||
}
|
||||
});
|
||||
|
||||
if ($manageModal.find('.error').length > 0) {
|
||||
$btn.addClass('disabled');
|
||||
} else {
|
||||
$btn.removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function highlightErrors() {
|
||||
$(manageModal).find('.error').addClass('error-highlight');
|
||||
}
|
||||
|
||||
function initActions() {
|
||||
var $manageModal = $(manageModal);
|
||||
var addStatusOptionBtn = '.add-status';
|
||||
var deleteStatusOptionBtn = '.status-item-icon-trash';
|
||||
var icon = '.status-item-icon';
|
||||
var emojis = '.emojis-picker>span';
|
||||
var statusInput = 'input.status-item-field';
|
||||
var buttonWrapper = '.button-wrapper';
|
||||
|
||||
$manageModal.on('click', addStatusOptionBtn, function() {
|
||||
var newStatusRow = $(statusTemplate()).insertBefore($(this));
|
||||
validateForm();
|
||||
setTimeout(function() {
|
||||
newStatusRow.css('height', '34px');
|
||||
}, 0);
|
||||
setTimeout(function() {
|
||||
newStatusRow.removeClass('loading');
|
||||
newStatusRow.find('input').focus();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$manageModal.on('click', deleteStatusOptionBtn, function() {
|
||||
// if existing item is deleted, flag it as deleted
|
||||
// if new item is deleted, remove it from HTML
|
||||
|
||||
var $statusRow = $(this).parent();
|
||||
var $emojis = $statusRow.next('.emojis-picker');
|
||||
var isNewRow = $statusRow.data('id') == null;
|
||||
|
||||
setTimeout(function() {
|
||||
$statusRow.addClass('loading');
|
||||
$statusRow.css('height', '0px');
|
||||
}, 0);
|
||||
setTimeout(function() {
|
||||
if (isNewRow) {
|
||||
$statusRow.remove();
|
||||
validateForm();
|
||||
} else {
|
||||
$statusRow.attr('data-removed', 'true');
|
||||
$statusRow.removeClass('loading');
|
||||
$statusRow.removeClass('error');
|
||||
validateForm();
|
||||
}
|
||||
$emojis.remove();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$manageModal.on('click', icon, function() {
|
||||
var $emojiPicker = $(this).parent().next('.emojis-picker');
|
||||
$emojiPicker.show();
|
||||
});
|
||||
|
||||
$manageModal.on('click', emojis, function() {
|
||||
var $clickedEmoji = $(this);
|
||||
var $iconField = $clickedEmoji.parent().prev().find('.status-item-icon');
|
||||
$clickedEmoji.parent().hide();
|
||||
$iconField.html($clickedEmoji.data('emoji-code'));
|
||||
$iconField.attr('data-icon', $clickedEmoji.data('emoji-code'));
|
||||
$iconField.trigger('data-attribute-changed', $iconField);
|
||||
});
|
||||
|
||||
$manageModal
|
||||
.on('keyup change', statusInput, function() {
|
||||
validateForm();
|
||||
})
|
||||
.on('data-attribute-changed columnModal::partialLoadedForRepositoryStatusValue', function() {
|
||||
validateForm();
|
||||
})
|
||||
.on('click', buttonWrapper, function() {
|
||||
highlightErrors();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
initActions();
|
||||
},
|
||||
checkValidation: () => {
|
||||
highlightErrors();
|
||||
return !($(manageModal).find('.error').length > 0);
|
||||
},
|
||||
loadParams: () => {
|
||||
var $modal = $(manageModal);
|
||||
var $statusItems;
|
||||
var repositoryColumnParams = {};
|
||||
|
||||
$statusItems = $modal.find('.status-item-container');
|
||||
// Load all new items
|
||||
// Load all existing items, delete flag included
|
||||
repositoryColumnParams.repository_status_items_attributes = [];
|
||||
|
||||
$.each($statusItems, function(index, value) {
|
||||
var $item = $(value);
|
||||
var id = $item.data('id');
|
||||
var removed = $item.data('removed');
|
||||
var icon = $item.find('.status-item-icon').data('icon');
|
||||
var status = $item.find('input.status-item-field').val();
|
||||
|
||||
if (removed && id) { // flag as item for removing
|
||||
repositoryColumnParams.repository_status_items_attributes
|
||||
.push({ id: id, _destroy: true });
|
||||
} else if (id) { // existing element, maybe values needs to be updated
|
||||
repositoryColumnParams.repository_status_items_attributes
|
||||
.push({ id: id, icon: icon, status: status });
|
||||
} else { // new element
|
||||
repositoryColumnParams.repository_status_items_attributes
|
||||
.push({ icon: icon, status: status });
|
||||
}
|
||||
});
|
||||
|
||||
return repositoryColumnParams;
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,17 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
var RepositoryTimeColumnType = (function() {
|
||||
return {
|
||||
init: () => {},
|
||||
checkValidation: () => {
|
||||
return true;
|
||||
},
|
||||
loadParams: () => {
|
||||
var isRange = $('#time-range').is(':checked');
|
||||
var columnType = $('#repository-column-data-type').val();
|
||||
if (isRange) {
|
||||
columnType = columnType.replace('Value', 'RangeValue');
|
||||
}
|
||||
return { column_type: columnType };
|
||||
}
|
||||
};
|
||||
}());
|
224
app/assets/javascripts/repository_columns/index.js
Normal file
224
app/assets/javascripts/repository_columns/index.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
/* global I18n HelperModule animateSpinner RepositoryListColumnType */
|
||||
/* global RepositoryStatusColumnType dropdownSelector */
|
||||
/* eslint-disable no-restricted-globals */
|
||||
var RepositoryColumns = (function() {
|
||||
var manageModal = '#manage-repository-column';
|
||||
var columnTypeClassNames = {
|
||||
RepositoryListValue: 'RepositoryListColumnType',
|
||||
RepositoryStatusValue: 'RepositoryStatusColumnType',
|
||||
RepositoryDateValue: 'RepositoryDateColumnType',
|
||||
RepositoryDateTimeValue: 'RepositoryDateTimeColumnType',
|
||||
RepositoryTimeValue: 'RepositoryDateTimeColumnType',
|
||||
RepositoryChecklistValue: 'RepositoryChecklistColumnType',
|
||||
RepositoryNumberValue: 'RepositoryNumberColumnType'
|
||||
};
|
||||
|
||||
function initColumnTypeSelector() {
|
||||
var $manageModal = $(manageModal);
|
||||
$manageModal.on('change', '#repository-column-data-type', function() {
|
||||
$('.column-type').hide();
|
||||
$('[data-column-type="' + $(this).val() + '"]').show();
|
||||
});
|
||||
}
|
||||
|
||||
function removeElementFromDom(column) {
|
||||
$('.repository-column-edtior .list-group-item[data-id="' + column.id + '"]').remove();
|
||||
if ($('.list-group-item').length === 0) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function initDeleteSubmitAction() {
|
||||
var $manageModal = $(manageModal);
|
||||
$manageModal.on('click', '#delete-repo-column-submit', function() {
|
||||
animateSpinner();
|
||||
$manageModal.modal('hide');
|
||||
$.ajax({
|
||||
url: $(this).data('delete-url'),
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
success: (result) => {
|
||||
removeElementFromDom(result);
|
||||
HelperModule.flashAlertMsg(result.message, 'success');
|
||||
animateSpinner(null, false);
|
||||
},
|
||||
error: (result) => {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(result.responseJSON.error, 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkData() {
|
||||
var currentPartial = $('#repository-column-data-type').val();
|
||||
|
||||
if (columnTypeClassNames[currentPartial]) {
|
||||
return eval(columnTypeClassNames[currentPartial])
|
||||
.checkValidation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function addSpecificParams(type, params) {
|
||||
var allParams = params;
|
||||
var columnParams;
|
||||
var specificParams;
|
||||
var currentPartial = $('#repository-column-data-type').val();
|
||||
|
||||
if (columnTypeClassNames[currentPartial]) {
|
||||
specificParams = eval(columnTypeClassNames[currentPartial]).loadParams();
|
||||
columnParams = Object.assign(params.repository_column, specificParams);
|
||||
allParams.repository_column = columnParams;
|
||||
}
|
||||
|
||||
return allParams;
|
||||
}
|
||||
|
||||
function insertNewListItem(column) {
|
||||
var attributes = column.attributes;
|
||||
var html = `<li class="list-group-item row" data-id="${column.id}">
|
||||
|
||||
<div class="col-xs-8">
|
||||
<span class="pull-left column-name">${attributes.name}</span>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<span class="controlls pull-right">
|
||||
<button class="btn btn-default edit-repo-column manage-repo-column"
|
||||
data-action="edit"
|
||||
data-modal-url="${attributes.edit_html_url}"
|
||||
>
|
||||
<span class="fas fa-pencil-alt"></span>
|
||||
${ I18n.t('libraries.repository_columns.index.edit_column')}
|
||||
</button>
|
||||
<button class="btn btn-default delete-repo-column manage-repo-column"
|
||||
data-action="destroy"
|
||||
data-modal-url="${attributes.destroy_html_url}"
|
||||
>
|
||||
<span class="fas fa-trash-alt"></span>
|
||||
${ I18n.t('libraries.repository_columns.index.delete_column')}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</li>`;
|
||||
|
||||
// remove element if already persent
|
||||
$('[data-id="' + column.id + '"]').remove();
|
||||
$(html).insertBefore('.repository-columns-body ul li:first');
|
||||
// remove 'no column' list item
|
||||
$('[data-attr="no-columns"]').remove();
|
||||
}
|
||||
|
||||
function updateListItem(column) {
|
||||
var name = column.attributes.name;
|
||||
$('li[data-id=' + column.id + ']').find('span').first().html(name);
|
||||
}
|
||||
|
||||
function initCreateSubmitAction() {
|
||||
var $manageModal = $(manageModal);
|
||||
$manageModal.on('click', '#new-repo-column-submit', function() {
|
||||
var url = $('#repository-column-data-type').find(':selected').data('create-url');
|
||||
var params = { repository_column: { name: $('#repository-column-name').val() } };
|
||||
var selectedType = $('#repository-column-data-type').val();
|
||||
params = addSpecificParams(selectedType, params);
|
||||
if (!checkData()) return;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: JSON.stringify(params),
|
||||
contentType: 'application/json',
|
||||
success: function(result) {
|
||||
var data = result.data;
|
||||
insertNewListItem(data);
|
||||
HelperModule.flashAlertMsg(data.attributes.message, 'success');
|
||||
$manageModal.modal('hide');
|
||||
},
|
||||
error: function(error) {
|
||||
$('#new-repository-column').renderFormErrors('repository_column', error.responseJSON.repository_column, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initEditSubmitAction() {
|
||||
var $manageModal = $(manageModal);
|
||||
$manageModal.on('click', '#update-repo-column-submit', function() {
|
||||
var url = $('#repository-column-data-type').find(':selected').data('edit-url');
|
||||
var params = { repository_column: { name: $('#repository-column-name').val() } };
|
||||
var selectedType = $('#repository-column-data-type').val();
|
||||
params = addSpecificParams(selectedType, params);
|
||||
if (!checkData()) return;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: JSON.stringify(params),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
success: function(result) {
|
||||
var data = result.data;
|
||||
updateListItem(data);
|
||||
HelperModule.flashAlertMsg(data.attributes.message, 'success');
|
||||
$manageModal.modal('hide');
|
||||
},
|
||||
error: function(error) {
|
||||
$('#new-repository-column').renderFormErrors('repository_column', error.responseJSON.repository_column, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initManageColumnModal() {
|
||||
var $manageModal = $(manageModal);
|
||||
$('.repository-column-edtior').on('click', '.manage-repo-column', function() {
|
||||
var button = $(this);
|
||||
var modalUrl = button.data('modal-url');
|
||||
var columnType;
|
||||
$.get(modalUrl, (data) => {
|
||||
$manageModal.modal('show').find('.modal-content').html(data.html)
|
||||
.find('#repository-column-name')
|
||||
.focus();
|
||||
columnType = $('#repository-column-data-type').val();
|
||||
dropdownSelector.init('#repository-column-data-type', {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
optionClass: 'custom-option',
|
||||
selectAppearance: 'simple'
|
||||
});
|
||||
$manageModal
|
||||
.trigger('columnModal::partialLoadedFor' + columnType);
|
||||
|
||||
if (button.data('action') === 'new') {
|
||||
$('[data-column-type="RepositoryTextValue"]').show();
|
||||
$('#new-repo-column-submit').show();
|
||||
} else {
|
||||
$('#update-repo-column-submit').show();
|
||||
$('[data-column-type=' + columnType + ']').show();
|
||||
}
|
||||
}).fail(function() {
|
||||
HelperModule.flashAlertMsg(I18n.t('libraries.repository_columns.no_permissions'), 'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.repository-columns-header').length > 0) {
|
||||
initColumnTypeSelector();
|
||||
initEditSubmitAction();
|
||||
initCreateSubmitAction();
|
||||
initDeleteSubmitAction();
|
||||
initManageColumnModal();
|
||||
RepositoryListColumnType.init();
|
||||
RepositoryStatusColumnType.init();
|
||||
RepositoryChecklistColumnType.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
RepositoryColumns.init();
|
||||
});
|
|
@ -1,225 +0,0 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
// @TODO refactor that eventually
|
||||
function initEditCoumnModal() {
|
||||
var modalID = '#manageRepositoryColumn';
|
||||
var colRadID = '#repository_column_data_type_repositorylistvalue';
|
||||
var tagsInputID = '[data-role="tagsinput"]';
|
||||
var formID = '[data-role="manage-repository-column-form"]';
|
||||
|
||||
$('[data-action="edit"]').off('click').on('click', function() {
|
||||
var editUrl = $(this).closest('li').attr('data-edit-url');
|
||||
$.get(editUrl, function(data) {
|
||||
$(data.html).appendTo('body').promise().done(function() {
|
||||
$(modalID).modal('show').promise().done(function() {
|
||||
|
||||
$(modalID).on('hidden.bs.modal', function () {
|
||||
// remove edit modal window
|
||||
$(modalID).remove();
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
|
||||
_initTagInput();
|
||||
setTimeout(function() {
|
||||
$('#repository_column_name').focus();
|
||||
}, 500)
|
||||
|
||||
if($(modalID).attr('data-edit-type') === 'RepositoryListValue') {
|
||||
var values = JSON.parse($(tagsInputID).attr('data-value'));
|
||||
$(colRadID).click().promise().done(function() {
|
||||
$.each(values, function(index, element) {
|
||||
$(tagsInputID).tagsinput('add', element);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-action="save"]').on('click', function() {
|
||||
if($(colRadID).is(':checked')) {
|
||||
$('#list_items').val($(tagsInputID).val());
|
||||
}
|
||||
|
||||
_processResponse($(formID), 'update', modalID);
|
||||
});
|
||||
});
|
||||
});
|
||||
}).fail(function(error) {
|
||||
HelperModule.flashAlertMsg(
|
||||
"<%= I18n.t("libraries.repository_columns.no_permissions") %>",
|
||||
'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteColumnModal() {
|
||||
$('[data-action="destroy"]').off('click').on('click', function() {
|
||||
var element = $(this);
|
||||
var modal_html = $("#deleteRepositoryColumn");
|
||||
$.get(element.closest('li').attr('data-destroy-url'), function(data) {
|
||||
modal_html.find('.modal-body').html(data.html)
|
||||
.promise()
|
||||
.done(function() {
|
||||
modal_html.modal('show');
|
||||
_initSubmitAction(modal_html, $(modal_html.find('form')));
|
||||
});
|
||||
}).fail(function(error) {
|
||||
HelperModule.flashAlertMsg(
|
||||
"<%= I18n.t("libraries.repository_columns.no_permissions") %>",
|
||||
'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// @TODO refactor that eventually
|
||||
function initNewColumnModal() {
|
||||
var modalID = '#manageRepositoryColumn';
|
||||
$('[data-action="new-column-modal"]').off('click').on('click', function() {
|
||||
var modalUrl = $(this).attr('data-modal-url');
|
||||
$.get(modalUrl, function(data) {
|
||||
$(data.html).appendTo('body').promise().done(function() {
|
||||
$(modalID).modal('show').promise().done(function() {
|
||||
|
||||
$(modalID).on('hidden.bs.modal', function () {
|
||||
// remove create new modal window
|
||||
$(modalID).remove();
|
||||
$('.modal-backdrop').remove();
|
||||
});
|
||||
|
||||
_initTagInput();
|
||||
setTimeout(function() {
|
||||
$('#repository_column_name').focus();
|
||||
}, 500);
|
||||
|
||||
$('[data-action="save"]').on('click', function() {
|
||||
var colRad = '#repository_column_data_type_repositorylistvalue';
|
||||
if($(colRad).is(':checked')) {
|
||||
$('#list_items')
|
||||
.val($('[data-role="tagsinput"]').val());
|
||||
}
|
||||
var form = $('[data-role="manage-repository-column-form"]');
|
||||
_processResponse(form, 'create', modalID);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* *********************************
|
||||
Helper methods
|
||||
********************************* */
|
||||
|
||||
function _insertNewListItem(column) {
|
||||
// remove element if already persent
|
||||
$('[data-id="' + column.id + '"]').remove();
|
||||
var html = '<li class="list-group-item row" data-id="' + column.id + '" ';
|
||||
html += 'data-destroy-url="' + column.destroy_html_url + '"';
|
||||
html += 'data-edit-url="' + column.edit_url + '"><div class="col-xs-8">';
|
||||
html += '<span class="pull-left column-name">' + column.name + '</span>';
|
||||
html += '</div><div class="col-xs-4"><span class="controlls pull-right">';
|
||||
html += '<button class="btn btn-default" data-action="edit">';
|
||||
html += '<span class="fas fa-pencil-alt"></span> ';
|
||||
html += '<%= I18n.t "libraries.repository_columns.index.edit_column" %></button> ';
|
||||
html += '<button class="btn btn-default delete-column" data-action="destroy">';
|
||||
html += '<span class="fas fa-trash-alt"></span> ';
|
||||
html += '<%= I18n.t "libraries.repository_columns.index.delete_column" %>';
|
||||
html += '</button></span></div></li>';
|
||||
$(html).insertBefore('.repository-columns-body ul li:first')
|
||||
.promise()
|
||||
.done(function() {
|
||||
initDeleteColumnModal();
|
||||
initEditCoumnModal();
|
||||
});
|
||||
// remove 'no column' list item
|
||||
$('[data-attr="no-columns"]').remove();
|
||||
}
|
||||
|
||||
function _replaceListItem(column) {
|
||||
$('.list-group-item[data-id="' + column.id + '"]')
|
||||
.find('span.pull-left').text(column.name);
|
||||
}
|
||||
|
||||
function _initTagInput() {
|
||||
$('[name="repository_column[data_type]"]')
|
||||
.on('click', function() {
|
||||
var listValueId = 'repository_column_data_type_repositorylistvalue';
|
||||
if($(this).attr('id') === listValueId) {
|
||||
$('[data-role="tagsinput"]').tagsinput({
|
||||
maxChars: <%= Constants::NAME_MAX_LENGTH %>,
|
||||
trimValue: true
|
||||
});
|
||||
$('.bootstrap-tagsinput').show();
|
||||
$('[data-role="tagsimput-label"]').show();
|
||||
} else {
|
||||
$('.bootstrap-tagsinput').hide();
|
||||
$('[data-role="tagsimput-label"]').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _removeElementFromDom(column) {
|
||||
$('.list-group-item[data-id="' + column.id + '"]').remove();
|
||||
if($('.list-group-item').length === 0) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function _initSubmitAction(modal, form) {
|
||||
modal.find('[data-action="delete"]').on('click', function() {
|
||||
form.submit();
|
||||
modal.modal('hide')
|
||||
animateSpinner();
|
||||
_processResponse(form, 'destroy');
|
||||
});
|
||||
}
|
||||
|
||||
function _processResponse(form, action, modalID) {
|
||||
form.on('ajax:success', function(e, data) {
|
||||
switch(action) {
|
||||
case 'destroy':
|
||||
_removeElementFromDom(data);
|
||||
break;
|
||||
case 'create':
|
||||
_insertNewListItem(data);
|
||||
break;
|
||||
case 'update':
|
||||
_replaceListItem(data);
|
||||
break;
|
||||
default:
|
||||
location.reload();
|
||||
}
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
animateSpinner(null, false);
|
||||
if (modalID) {
|
||||
$(modalID).modal('hide');
|
||||
}
|
||||
}).on('ajax:error', function(e, xhr) {
|
||||
animateSpinner(null, false);
|
||||
if (modalID) {
|
||||
if(xhr.responseJSON.message.hasOwnProperty('repository_list_items')) {
|
||||
var message = xhr.responseJSON.message['repository_list_items'];
|
||||
$('.dnd-error').remove();
|
||||
$('#manageRepositoryColumn ').find('.bootstrap-tagsinput').after(
|
||||
"<i class='dnd-error'>" + message + "</i>"
|
||||
);
|
||||
} else {
|
||||
var field = { "name": xhr.responseJSON.message }
|
||||
$(form).renderFormErrors('repository_column', field, true, e);
|
||||
}
|
||||
} else {
|
||||
HelperModule.flashAlertMsg(xhr.responseJSON.message, 'danger');
|
||||
}
|
||||
});
|
||||
if (modalID) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
/* *********************************
|
||||
Initializers
|
||||
********************************* */
|
||||
|
||||
initEditCoumnModal();
|
||||
initDeleteColumnModal();
|
||||
initNewColumnModal();
|
||||
})();
|
|
@ -1,7 +1,10 @@
|
|||
const GLOBAL_CONSTANTS = {
|
||||
NAME_TRUNCATION_LENGTH: <%= Constants::NAME_TRUNCATION_LENGTH %>,
|
||||
NAME_MAX_LENGTH: <%= Constants::NAME_MAX_LENGTH %>,
|
||||
NAME_MIN_LENGTH: <%= Constants::NAME_MIN_LENGTH %>,
|
||||
FILENAME_TRUNCATION_LENGTH: <%= Constants::FILENAME_TRUNCATION_LENGTH %>,
|
||||
FILE_MAX_SIZE_MB: <%= Rails.configuration.x.file_max_size_mb %>,
|
||||
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
|
||||
REPOSITORY_LIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN %>,
|
||||
REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN %>
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
data-placeholder // Search placeholder
|
||||
data-disable-on-load // Disable input after initialization
|
||||
data-select-all-button // Text for select all button
|
||||
data-combine-tags // Combine multiple tags to one (only for tags)
|
||||
data-combine-tags // Combine multiple tags to one (in simple mode gives you multiple select)
|
||||
data-select-multiple-all-selected // Text for combine tags, when all selected
|
||||
data-select-multiple-name // Text for combine tags, when select more than one tag
|
||||
data-view-mode // Run in view mode
|
||||
|
@ -55,16 +55,29 @@ var dropdownSelector = (function() {
|
|||
var containerPosition = container[0].getBoundingClientRect().top;
|
||||
var containerHeight = container[0].getBoundingClientRect().height;
|
||||
var containerWidth = container[0].getBoundingClientRect().width;
|
||||
var bottomSpace = windowHeight - containerPosition - containerHeight;
|
||||
if (bottomSpace < 280) {
|
||||
var bottomSpace;
|
||||
var modalContainer = container.closest('.modal-dialog');
|
||||
var modalContainerBottom = 0;
|
||||
var maxHeight = 0;
|
||||
|
||||
if (modalContainer.length) {
|
||||
windowHeight = modalContainer.height() + modalContainer[0].getBoundingClientRect().top;
|
||||
modalContainerBottom = modalContainer[0].getBoundingClientRect().bottom;
|
||||
maxHeight += modalContainerBottom;
|
||||
}
|
||||
|
||||
bottomSpace = windowHeight - containerPosition - containerHeight;
|
||||
|
||||
if ((modalContainerBottom + bottomSpace) < 280) {
|
||||
container.addClass('inverse');
|
||||
container.find('.dropdown-container').css('max-height', `${(containerPosition - 122)}px`)
|
||||
container.find('.dropdown-container').css('max-height', `${(containerPosition - 122 + maxHeight)}px`)
|
||||
.css('margin-bottom', `${(containerPosition * -1)}px`)
|
||||
.css('width', `${containerWidth}px`);
|
||||
} else {
|
||||
container.removeClass('inverse');
|
||||
container.find('.dropdown-container').css('max-height', `${(bottomSpace - 32)}px`)
|
||||
.css('width', '');
|
||||
container.find('.dropdown-container').css('max-height', `${(bottomSpace - 32 + maxHeight)}px`)
|
||||
.css('width', `${containerWidth}px`)
|
||||
.css('margin-top', `${(bottomSpace * -1)}px`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +136,7 @@ var dropdownSelector = (function() {
|
|||
if (mode) {
|
||||
updateCurrentData(container, []);
|
||||
updateTags(selector, container, { skipChange: true });
|
||||
searchFieldValue.attr('placeholder', selector.data('disable-placeholder'));
|
||||
searchFieldValue.attr('placeholder', selector.data('disable-placeholder') || '');
|
||||
container.addClass('disabled').removeClass('open')
|
||||
.find('.search-field').val('')
|
||||
.prop('disabled', true);
|
||||
|
@ -223,11 +236,17 @@ var dropdownSelector = (function() {
|
|||
|
||||
if (pressedKey === 38) {
|
||||
if (selectedOption.prev('.dropdown-option').length) {
|
||||
selectedOption.removeClass('highlight').prev('.dropdown-option').addClass('highlight');
|
||||
selectedOption.removeClass('highlight').prev().addClass('highlight');
|
||||
}
|
||||
if (selectedOption.prev('.delimiter').length) {
|
||||
selectedOption.removeClass('highlight').prev().prev().addClass('highlight');
|
||||
}
|
||||
} else if (pressedKey === 40) {
|
||||
if (selectedOption.next('.dropdown-option').length) {
|
||||
selectedOption.removeClass('highlight').next('.dropdown-option').addClass('highlight');
|
||||
selectedOption.removeClass('highlight').next().addClass('highlight');
|
||||
}
|
||||
if (selectedOption.next('.delimiter').length) {
|
||||
selectedOption.removeClass('highlight').next().next().addClass('highlight');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -259,7 +278,7 @@ var dropdownSelector = (function() {
|
|||
$(`
|
||||
<div class="dropdown-container"></div>
|
||||
<div class="input-field">
|
||||
<input type="text" class="search-field" placeholder="${selectElement.data('placeholder')}"></input>
|
||||
<input type="text" class="search-field" data-options-selected=0 placeholder="${selectElement.data('placeholder') || ''}"></input>
|
||||
${prepareCustomDropdownIcon(config)}
|
||||
</div>
|
||||
<input type="hidden" class="data-field" value="[]">
|
||||
|
@ -362,12 +381,15 @@ var dropdownSelector = (function() {
|
|||
});
|
||||
|
||||
// When user will resize browser we must check dropdown position
|
||||
$(window).resize(function() { updateDropdownDirection(selectElement, dropdownContainer); });
|
||||
$(window).resize(() => { updateDropdownDirection(selectElement, dropdownContainer); });
|
||||
$(window).scroll(() => { updateDropdownDirection(selectElement, dropdownContainer); });
|
||||
|
||||
// When user will click away, we must close dropdown
|
||||
$(window).click(() => {
|
||||
if (dropdownContainer.hasClass('open') && config.onClose) {
|
||||
if (dropdownContainer.hasClass('open')) {
|
||||
dropdownContainer.find('.search-field').val('');
|
||||
}
|
||||
if (dropdownContainer.hasClass('open') && config.onClose) {
|
||||
config.onClose();
|
||||
}
|
||||
dropdownContainer.removeClass('open active');
|
||||
|
@ -436,6 +458,11 @@ var dropdownSelector = (function() {
|
|||
`);
|
||||
}
|
||||
|
||||
// Draw delimiter object
|
||||
function drawDelimiter() {
|
||||
return $('<div class="delimiter"></div>');
|
||||
}
|
||||
|
||||
// Draw group object
|
||||
function drawGroup(group) {
|
||||
return $(`
|
||||
|
@ -452,13 +479,14 @@ var dropdownSelector = (function() {
|
|||
if (selector.data('config').singleSelect) {
|
||||
$container.find('.dropdown-option').removeClass('select');
|
||||
updateCurrentData($container, []);
|
||||
selector.val($(this).data('value')).change();
|
||||
}
|
||||
$(this).toggleClass('select');
|
||||
saveData(selector, $container);
|
||||
}
|
||||
|
||||
// Remove placeholder from option container
|
||||
container.find('.dropdown-group, .dropdown-option, .empty-dropdown').remove();
|
||||
container.find('.dropdown-group, .dropdown-option, .empty-dropdown, .delimiter').remove();
|
||||
if (!data) return;
|
||||
|
||||
if (data.length > 0) {
|
||||
|
@ -495,7 +523,12 @@ var dropdownSelector = (function() {
|
|||
} else {
|
||||
// For simple options we only draw them
|
||||
$.each(data, function(oi, option) {
|
||||
var optionElement = drawOption(selector, option);
|
||||
var optionElement;
|
||||
if (option.delimiter) {
|
||||
drawDelimiter().appendTo(container.find('.dropdown-container'));
|
||||
return;
|
||||
}
|
||||
optionElement = drawOption(selector, option);
|
||||
optionElement.click(clickOption);
|
||||
optionElement.appendTo(container.find('.dropdown-container'));
|
||||
});
|
||||
|
@ -527,7 +560,7 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
|
||||
// First we clear search field
|
||||
container.find('.search-field').val('');
|
||||
if (selector.data('config').singleSelect) container.find('.search-field').val('');
|
||||
|
||||
// Now we check all options in dropdown for selection and add them to array
|
||||
$.each(container.find('.dropdown-container .dropdown-option'), function(oi, option) {
|
||||
|
@ -558,8 +591,6 @@ var dropdownSelector = (function() {
|
|||
updateCurrentData(container, selectArray);
|
||||
// Redraw tags
|
||||
updateTags(selector, container, { select: true });
|
||||
// Reload options in option container
|
||||
loadData(selector, container);
|
||||
}
|
||||
|
||||
// Refresh tags in input field
|
||||
|
@ -632,7 +663,8 @@ var dropdownSelector = (function() {
|
|||
|
||||
// If we have alteast one tag, we need to remove placeholder from search field
|
||||
searchFieldValue.attr('placeholder',
|
||||
selectedOptions.length > 0 ? '' : selector.data('placeholder'));
|
||||
selectedOptions.length > 0 ? '' : (selector.data('placeholder') || ''));
|
||||
searchFieldValue.attr('data-options-selected', selectedOptions.length);
|
||||
|
||||
// Add stretch class for visual improvments
|
||||
if (!selector.data('combine-tags')) {
|
||||
|
@ -701,7 +733,11 @@ var dropdownSelector = (function() {
|
|||
} else {
|
||||
options = filterOptions(selector, container, selector.find('option'));
|
||||
$.each(options, function(oi, option) {
|
||||
result.push({ label: option.innerHTML, value: option.value });
|
||||
result.push({
|
||||
label: option.innerHTML,
|
||||
value: option.value,
|
||||
delimiter: option.dataset.delimiter
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
@ -751,7 +787,6 @@ var dropdownSelector = (function() {
|
|||
values = $.map(getCurrentData($(selector).next()), (v) => {
|
||||
return v.value;
|
||||
});
|
||||
|
||||
if ($(selector).data('config').singleSelect) return values[0];
|
||||
|
||||
return values;
|
||||
|
|
|
@ -42,7 +42,7 @@ var renderFormError = function(ev, input, errMsgs, clearErr, errAttributes) {
|
|||
})).join('<br />');
|
||||
var $errSpan = "<span class='help-block'" +
|
||||
errAttributes + '>' + errorText + '</span>';
|
||||
$formGroup.append($errSpan);
|
||||
$(input).after($errSpan);
|
||||
}
|
||||
|
||||
var $parent;
|
||||
|
|
|
@ -6,7 +6,7 @@ var PerfectSb = (function() {
|
|||
function init() {
|
||||
$.each($('.perfect-scrollbar'), function(index, object) {
|
||||
var ps;
|
||||
activePSB.lenght = 0;
|
||||
activePSB.length = 0;
|
||||
ps = new PerfectScrollbar(object, { wheelSpeed: 0.5, minScrollbarLength: 20 });
|
||||
activePSB.push(ps);
|
||||
});
|
||||
|
|
|
@ -94,11 +94,17 @@
|
|||
var mouse = { x: e.clientX, y: e.clientY };
|
||||
$('.popover.tooltip-open').each(function(i, obj) {
|
||||
var tooltipObj = '*[data-tooltip-id="' + obj.dataset.popoverId + '"]';
|
||||
var objHeight = $(tooltipObj)[0].clientHeight;
|
||||
var objWidth = $(tooltipObj)[0].clientWidth;
|
||||
var objLeft = $(tooltipObj)[0].offsetLeft;
|
||||
var objTop = $(tooltipObj)[0].offsetTop;
|
||||
var objCorners = {
|
||||
var objHeight;
|
||||
var objWidth;
|
||||
var objLeft;
|
||||
var objTop;
|
||||
var objCorners;
|
||||
if ($(tooltipObj).length === 0) return;
|
||||
objHeight = $(tooltipObj)[0].clientHeight;
|
||||
objWidth = $(tooltipObj)[0].clientWidth;
|
||||
objLeft = $(tooltipObj)[0].offsetLeft;
|
||||
objTop = $(tooltipObj)[0].offsetTop;
|
||||
objCorners = {
|
||||
tl: { x: objLeft, y: objTop },
|
||||
tr: { x: (objLeft + objWidth), y: objTop },
|
||||
bl: { x: objLeft, y: (objTop + objHeight) },
|
||||
|
|
|
@ -154,7 +154,7 @@ $btn-border-radius-small: $border-radius-small;
|
|||
// $input-bg: #fff;
|
||||
// $input-bg-disabled: $gray-lighter;
|
||||
// $input-color: $gray;
|
||||
// $input-border: #ccc;
|
||||
$input-border: #ccc;
|
||||
// $input-border-radius: $border-radius-base;
|
||||
// $input-border-radius-large: $border-radius-large;
|
||||
// $input-border-radius-small: $border-radius-small;
|
||||
|
|
|
@ -61,6 +61,14 @@
|
|||
padding: 3px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.repository-status-value-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.dropdown-selector-container {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.repository-cog {
|
||||
|
@ -140,6 +148,128 @@
|
|||
}
|
||||
}
|
||||
|
||||
.asset-value-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.fas {
|
||||
font-size: 18px;
|
||||
min-width: 18px;
|
||||
}
|
||||
|
||||
.image-icon {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.file-preview-link {
|
||||
min-width: 140px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-editing {
|
||||
width: 200px;
|
||||
|
||||
.file-upload-button {
|
||||
background-color: $color-white;
|
||||
border: solid 1px;
|
||||
border-radius: 3px;
|
||||
height: 34px;
|
||||
line-height: 32px;
|
||||
position: relative;
|
||||
|
||||
&.new-file {
|
||||
.icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fa-trash {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
.label-asset {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
|
||||
.fa-trash {
|
||||
background-color: $color-white;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
width: 34px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fa-trash {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $brand-danger;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&::after {
|
||||
background-color: $color-white;
|
||||
bottom: 0;
|
||||
color: $brand-danger;
|
||||
content: "\f071";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 501;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
bottom: -15px;
|
||||
color: $brand-danger;
|
||||
content: attr(data-error-text);
|
||||
left: 0;
|
||||
line-height: 15px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
input[type=file] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: none;
|
||||
font-weight: normal;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbarButtonsDatatable {
|
||||
.view-only-label {
|
||||
opacity: .6;
|
||||
|
@ -255,3 +385,4 @@
|
|||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
app/assets/stylesheets/repository/repository_table.scss
Normal file
30
app/assets/stylesheets/repository/repository_table.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
// scss-lint:disable SelectorDepth SelectorFormat QualifyingElement
|
||||
// scss-lint:disable NestingDepth ImportantRule
|
||||
|
||||
@import "constants";
|
||||
|
||||
.repository-table {
|
||||
// Cells
|
||||
|
||||
// Checklists
|
||||
.checklist-dropdown {
|
||||
.dropdown-menu {
|
||||
.checklist-item {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DateTime
|
||||
.dateonly {
|
||||
input.time-part {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.timeonly {
|
||||
input.date-part {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
26
app/assets/stylesheets/repository_columns/index.scss
Normal file
26
app/assets/stylesheets/repository_columns/index.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
// scss-lint:disable IdSelector
|
||||
|
||||
@import "constants";
|
||||
|
||||
#manage-repository-column {
|
||||
.modal-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.range-label {
|
||||
left: 3px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
#repository-column-data-type + .dropdown-selector-container {
|
||||
.custom-option {
|
||||
padding: 0 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
app/assets/stylesheets/repository_columns/list_type.scss
Normal file
52
app/assets/stylesheets/repository_columns/list_type.scss
Normal file
|
@ -0,0 +1,52 @@
|
|||
// scss-lint:disable NestingDepth
|
||||
|
||||
@import "constants";
|
||||
|
||||
.dropdown-preview {
|
||||
align-items: center;
|
||||
background-color: $color-concrete;
|
||||
border: 1px solid $color-gainsboro;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 100px 5px;
|
||||
position: relative;
|
||||
|
||||
.field-name {
|
||||
color: $color-silver-chalice;
|
||||
left: 0;
|
||||
padding: 0 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.preview-block {
|
||||
flex-basis: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.limit-counter-container {
|
||||
bottom: 0;
|
||||
color: $color-silver-chalice;
|
||||
left: 100%;
|
||||
line-height: 34px;
|
||||
margin-left: 15px;
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
|
||||
.list-items-limit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.error-to-many-items {
|
||||
.list-items-count {
|
||||
color: $brand-danger;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-items-limit {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
app/assets/stylesheets/repository_columns/status_type.scss
Normal file
109
app/assets/stylesheets/repository_columns/status_type.scss
Normal file
|
@ -0,0 +1,109 @@
|
|||
@import "constants";
|
||||
|
||||
.status-item-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 34px;
|
||||
margin-bottom: 5px;
|
||||
transition: .3s;
|
||||
|
||||
.status-item-field {
|
||||
border: 1px solid $input-border;
|
||||
border-left: 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
height: 34px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.status-item-icon {
|
||||
border: 1px solid $input-border;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 34px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
width: 34px;
|
||||
|
||||
|
||||
&:not([data-icon])::before {
|
||||
content: "\f06a";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
.status-item-icon-trash {
|
||||
color: $color-silver-chalice;
|
||||
padding: 0 10px;
|
||||
width: 34px;
|
||||
|
||||
&:hover {
|
||||
color: $color-dove-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
height: 0;
|
||||
|
||||
* {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-removed="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.error.error-highlight {
|
||||
.status-item-icon,
|
||||
.status-item-field {
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
|
||||
.status-item-icon {
|
||||
border-right-color: $input-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-status {
|
||||
align-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
color: $color-silver-chalice;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 34px;
|
||||
line-height: 32px;
|
||||
margin-right: 32px;
|
||||
transition: .2s;
|
||||
|
||||
.fa-plus {
|
||||
display: inline-block;
|
||||
height: 34px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $color-gainsboro;
|
||||
}
|
||||
|
||||
.add-status-label {
|
||||
flex-grow: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.emojis-picker {
|
||||
border: 1px solid $color-gainsboro;
|
||||
display: none;
|
||||
font-size: 30px;
|
||||
height: 40px;
|
||||
width: 211px;
|
||||
}
|
|
@ -40,6 +40,15 @@ input[type="checkbox"].simple-checkbox {
|
|||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
|
||||
+ .checkbox-label {
|
||||
color: $color-silver-chalice;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden + .checkbox-label {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -52,9 +52,12 @@
|
|||
.ds-simple {
|
||||
font-size: 14px;
|
||||
line-height: 28px;
|
||||
overflow: hidden;
|
||||
padding-left: 5px;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
transition: .3s;
|
||||
white-space: nowrap;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
@ -106,8 +109,8 @@
|
|||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 0 4px 0 rgba(0, 0, 0, 0.08);
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: calc(100% - 30px);
|
||||
position: fixed;
|
||||
bottom: calc(100% - 30px);
|
||||
transition: .2s;
|
||||
transition-property: top, bottom, box-shadow;
|
||||
width: 100%;
|
||||
|
@ -119,6 +122,12 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.delimiter {
|
||||
background: $color-alto;
|
||||
height: 1px;
|
||||
margin: 5px 16px;
|
||||
}
|
||||
|
||||
.dropdown-select-all {
|
||||
background: $color-white;
|
||||
border: 0;
|
||||
|
@ -158,7 +167,7 @@
|
|||
cursor: pointer;
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
padding: 3px 10px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
|
@ -227,6 +236,22 @@
|
|||
bottom: 3px;
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
&[data-options-selected="0"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-simple {
|
||||
.tag-label {
|
||||
overflow: hiddens;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fa-times {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +273,7 @@
|
|||
|
||||
.dropdown-container {
|
||||
border-top: 0;
|
||||
bottom: auto;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 0 4px 0 rgba(0, 0, 0, 0.08);
|
||||
display: block;
|
||||
top: 100%;
|
||||
|
@ -274,6 +300,10 @@
|
|||
.search-field {
|
||||
display: block;
|
||||
line-height: 14px;
|
||||
|
||||
&[data-options-selected="0"] {
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-simple {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.repository-columns-body {
|
||||
.delete-column {
|
||||
.delete-repo-column {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.repository-table {
|
||||
tbody {
|
||||
tr:hover {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
|
||||
.editing {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.repository-row-edit-icon {
|
||||
opacity: 0;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
tr:hover .repository-row-edit-icon {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-input-file-field-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -14,6 +14,7 @@ module Api
|
|||
def index
|
||||
columns = @inventory.repository_columns
|
||||
.includes(:repository_list_items)
|
||||
.includes(:repository_status_items)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
render jsonapi: columns,
|
||||
|
|
76
app/controllers/api/v1/inventory_status_items_controller.rb
Normal file
76
app/controllers/api/v1/inventory_status_items_controller.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class InventoryStatusItemsController < BaseController
|
||||
before_action :load_team, :load_inventory, :load_inventory_column, :check_column_type
|
||||
before_action :load_inventory_status_item, only: %i(show update destroy)
|
||||
before_action :check_manage_permissions, only: %i(create update destroy)
|
||||
|
||||
def index
|
||||
status_items = @inventory_column.repository_status_items
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
render jsonapi: status_items, each_serializer: InventoryStatusItemSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
status_item = @inventory_column.repository_status_items.create!(inventory_status_item_params)
|
||||
render jsonapi: status_item,
|
||||
serializer: InventoryStatusItemSerializer,
|
||||
status: :created
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi: @inventory_status_item,
|
||||
serializer: InventoryStatusItemSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@inventory_status_item.attributes = update_inventory_status_item_params
|
||||
if @inventory_status_item.changed? && @inventory_status_item.save!
|
||||
render jsonapi: @inventory_status_item,
|
||||
serializer: InventoryStatusItemSerializer
|
||||
else
|
||||
render body: nil, status: :no_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@inventory_status_item.destroy!
|
||||
render body: nil, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_column_type
|
||||
raise TypeError unless @inventory_column.data_type == 'RepositoryStatusValue'
|
||||
end
|
||||
|
||||
def load_inventory_status_item
|
||||
@inventory_status_item = @inventory_column.repository_status_items.find(params.require(:id))
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
raise PermissionError.new(RepositoryStatusItem, :manage) unless can_manage_repository_column?(@inventory_column)
|
||||
end
|
||||
|
||||
def inventory_status_item_params
|
||||
raise TypeError unless params.require(:data).require(:type) == 'inventory_status_items'
|
||||
|
||||
params.require(:data).require(:attributes)
|
||||
params.permit(data: { attributes: %i(status icon) })[:data].merge(
|
||||
created_by: @current_user,
|
||||
last_modified_by: @current_user,
|
||||
repository: @inventory
|
||||
)
|
||||
end
|
||||
|
||||
def update_inventory_status_item_params
|
||||
raise IDMismatchError unless params.require(:data).require(:id).to_i == params[:id].to_i
|
||||
|
||||
inventory_status_item_params[:attributes]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class AssetColumnsController < BaseColumnsController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_column, only: %i(update destroy)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy)
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryAssetValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class BaseColumnsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_repository
|
||||
|
||||
private
|
||||
|
||||
def load_repository
|
||||
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
|
||||
render_404 unless @repository
|
||||
end
|
||||
|
||||
def load_column
|
||||
@repository_column = @repository.repository_columns.find_by(id: params[:id])
|
||||
render_404 unless @repository_column
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_create_repository_columns?(@repository)
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_manage_repository_column?(@repository_column)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class ChecklistColumnsController < BaseColumnsController
|
||||
before_action :load_column, only: %i(update destroy items)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy items)
|
||||
helper_method :delimiters
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryChecklistValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateChecklistColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def items
|
||||
column_checklist_items = @repository_column.repository_checklist_items
|
||||
.where('data ILIKE ?',
|
||||
"%#{search_params[:query]}%")
|
||||
.select(:id, :data)
|
||||
.order(data: :asc)
|
||||
|
||||
render json: column_checklist_items.map { |i| { value: i.id, label: escape_input(i.data) } }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:query, :column_id)
|
||||
end
|
||||
|
||||
def repository_column_params
|
||||
params
|
||||
.require(:repository_column)
|
||||
.permit(:name, metadata: [:delimiter], repository_checklist_items_attributes: %i(data))
|
||||
end
|
||||
|
||||
def delimiters
|
||||
Constants::REPOSITORY_LIST_ITEMS_DELIMITERS
|
||||
.split(',')
|
||||
.map { |e| Hash[t('libraries.manange_modal_column.list_type.delimiters.' + e), e] }
|
||||
.inject(:merge)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class DateTimeColumnsController < BaseColumnsController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_column, only: %i(update destroy)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy)
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: column_type_param,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name)
|
||||
end
|
||||
|
||||
def column_type_param
|
||||
params.require(:repository_column).require(:column_type)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class ListColumnsController < BaseColumnsController
|
||||
before_action :load_column, only: %i(update destroy items)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy items)
|
||||
helper_method :delimiters
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryListValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateListColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def items
|
||||
column_list_items = @repository_column.repository_list_items
|
||||
.where('data ILIKE ?',
|
||||
"%#{search_params[:query]}%")
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select(:id, :data)
|
||||
|
||||
render json: column_list_items.map { |i| { value: i.id, label: escape_input(i.data) } }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:query, :column_id)
|
||||
end
|
||||
|
||||
def repository_column_params
|
||||
params
|
||||
.require(:repository_column)
|
||||
.permit(:name, metadata: [:delimiter], repository_list_items_attributes: %i(data))
|
||||
end
|
||||
|
||||
def delimiters
|
||||
Constants::REPOSITORY_LIST_ITEMS_DELIMITERS
|
||||
.split(',')
|
||||
.map { |e| Hash[t('libraries.manange_modal_column.list_type.delimiters.' + e), e] }
|
||||
.inject(:merge)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class NumberColumnsController < BaseColumnsController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_column, only: %i(update destroy)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy)
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryNumberValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name, metadata: [:decimals])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class StatusColumnsController < BaseColumnsController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_column, only: %i(update destroy items)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy items)
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryStatusValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: update_repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def items
|
||||
column_status_items = @repository_column.repository_status_items
|
||||
.where('status ILIKE ?',
|
||||
"%#{search_params[:query]}%")
|
||||
.select(:id, :icon, :status)
|
||||
|
||||
render json: column_status_items
|
||||
.map { |i| { value: i.id, label: "#{i.icon} #{escape_input(i.status)}" } }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:query, :column_id)
|
||||
end
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name, repository_status_items_attributes: %i(status icon))
|
||||
end
|
||||
|
||||
def update_repository_column_params
|
||||
params.require(:repository_column).permit(:name, repository_status_items_attributes: %i(id status icon _destroy))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class TextColumnsController < BaseColumnsController
|
||||
include InputSanitizeHelper
|
||||
before_action :load_column, only: %i(update destroy)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy)
|
||||
|
||||
def create
|
||||
service = RepositoryColumns::CreateColumnService
|
||||
.call(user: current_user, repository: @repository, team: current_team,
|
||||
column_type: Extends::REPOSITORY_DATA_TYPES[:RepositoryTextValue],
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :created, creating: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = RepositoryColumns::UpdateColumnService
|
||||
.call(user: current_user,
|
||||
team: current_team,
|
||||
column: @repository_column,
|
||||
params: repository_column_params)
|
||||
|
||||
if service.succeed?
|
||||
render json: service.column, status: :ok, editing: true
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = RepositoryColumns::DeleteColumnService
|
||||
.call(user: current_user, team: current_team, column: @repository_column)
|
||||
|
||||
if service.succeed?
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: service.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,7 @@
|
|||
class RepositoryColumnsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
include RepositoryColumnsHelper
|
||||
|
||||
ACTIONS = %i(
|
||||
create index create_html available_asset_type_columns available_columns
|
||||
).freeze
|
||||
|
@ -21,7 +23,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'repository_columns/manage_column_modal.html.erb'
|
||||
partial: 'repository_columns/manage_column_modal_content.html.erb'
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -73,15 +75,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'repository_columns/manage_column_modal.html.erb'
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
render json: { html: render_to_string(partial: 'repository_columns/manage_column_modal_content.html.erb') }
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
class RepositoryListItemsController < ApplicationController
|
||||
before_action :load_vars, only: :search
|
||||
|
||||
def search
|
||||
column_list_items = @repository_column.repository_list_items
|
||||
.where('data ILIKE ?',
|
||||
"%#{search_params[:q]}%")
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select(:id, :data)
|
||||
|
||||
render json: { list_items: column_list_items }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:q, :column_id)
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@repository_column = RepositoryColumn.find_by_id(search_params[:column_id])
|
||||
repository = @repository_column.repository if @repository_column
|
||||
unless @repository_column&.data_type == 'RepositoryListValue'
|
||||
render_404 and return
|
||||
end
|
||||
render_403 unless can_manage_repository_rows?(repository)
|
||||
end
|
||||
end
|
|
@ -37,38 +37,18 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
record = RepositoryRow.new(repository: @repository,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user)
|
||||
errors = { default_fields: [],
|
||||
repository_cells: [] }
|
||||
service = RepositoryRows::CreateRepositoryRowService
|
||||
.call(repository: @repository, user: current_user, params: update_params)
|
||||
|
||||
record.transaction do
|
||||
record.name = record_params[:repository_row_name] unless record_params[:repository_row_name].blank?
|
||||
errors[:default_fields] = record.errors.messages unless record.save
|
||||
if cell_params
|
||||
cell_params.each do |key, value|
|
||||
next if create_cell_value(record, key, value, errors).nil?
|
||||
end
|
||||
end
|
||||
raise ActiveRecord::Rollback if errors[:repository_cells].any?
|
||||
end
|
||||
if service.succeed?
|
||||
log_activity(:create_item_inventory, service.repository_row) if service.succeed?
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if errors[:default_fields].empty? && errors[:repository_cells].empty?
|
||||
log_activity(:create_item_inventory, record)
|
||||
|
||||
render json: { id: record.id,
|
||||
flash: t('repositories.create.success_flash',
|
||||
record: escape_input(record.name),
|
||||
repository: escape_input(@repository.name)) },
|
||||
status: :ok
|
||||
else
|
||||
render json: errors,
|
||||
status: :bad_request
|
||||
end
|
||||
end
|
||||
render json: { id: service.repository_row.id, flash: t('repositories.create.success_flash',
|
||||
record: escape_input(service.repository_row.name),
|
||||
repository: escape_input(@repository.name)) },
|
||||
status: :ok
|
||||
else
|
||||
render json: service.errors, status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,156 +97,18 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
errors = {
|
||||
default_fields: [],
|
||||
repository_cells: []
|
||||
}
|
||||
row_update = RepositoryRows::UpdateRepositoryRowService
|
||||
.call(repository_row: @record, user: current_user, params: update_params)
|
||||
|
||||
@record.transaction do
|
||||
@record.name = record_params[:repository_row_name].blank? ? nil : record_params[:repository_row_name]
|
||||
errors[:default_fields] = @record.errors.messages unless @record.save
|
||||
if cell_params
|
||||
cell_params.each do |key, value|
|
||||
existing = @record.repository_cells.detect do |c|
|
||||
c.repository_column_id == key.to_i
|
||||
end
|
||||
if existing
|
||||
# Cell exists and new value present, so update value
|
||||
if existing.value_type == 'RepositoryListValue'
|
||||
item = RepositoryListItem.where(
|
||||
repository_column: existing.repository_column
|
||||
).find(value) unless value == '-1'
|
||||
if item
|
||||
existing.value.update_attribute(
|
||||
:repository_list_item_id, item.id
|
||||
)
|
||||
else
|
||||
existing.delete
|
||||
end
|
||||
elsif existing.value_type == 'RepositoryAssetValue'
|
||||
existing.value.destroy && next if remove_file_columns_params.include?(key)
|
||||
if existing.value.asset.update(file: value)
|
||||
existing.value.asset.created_by = current_user
|
||||
existing.value.asset.last_modified_by = current_user
|
||||
existing.value.asset.post_process_file(current_team)
|
||||
else
|
||||
errors[:repository_cells] << {
|
||||
"#{existing.repository_column_id}": { data: existing.value.asset.errors.messages[:file].first }
|
||||
}
|
||||
end
|
||||
else
|
||||
existing.value.destroy && next if value == ''
|
||||
existing.value.data = value
|
||||
if existing.value.save
|
||||
record_annotation_notification(@record, existing)
|
||||
else
|
||||
errors[:repository_cells] << {
|
||||
"#{existing.repository_column_id}":
|
||||
existing.value.errors.messages
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
next if value == ''
|
||||
# Looks like it is a new cell, so we need to create new value, cell
|
||||
# will be created automatically
|
||||
next if create_cell_value(@record, key, value, errors).nil?
|
||||
end
|
||||
end
|
||||
else
|
||||
@record.repository_cells.each { |c| c.value.destroy }
|
||||
end
|
||||
raise ActiveRecord::Rollback if errors[:repository_cells].any?
|
||||
end
|
||||
if row_update.succeed?
|
||||
log_activity(:edit_item_inventory, @record) if row_update.record_updated
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if errors[:default_fields].empty? && errors[:repository_cells].empty?
|
||||
# Row sucessfully updated, so sending response to client
|
||||
log_activity(:edit_item_inventory, @record)
|
||||
|
||||
render json: {
|
||||
id: @record.id,
|
||||
flash: t(
|
||||
'repositories.update.success_flash',
|
||||
record: escape_input(@record.name),
|
||||
repository: escape_input(@repository.name)
|
||||
)
|
||||
},
|
||||
status: :ok
|
||||
else
|
||||
# Errors
|
||||
render json: errors,
|
||||
status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_cell_value(record, key, value, errors)
|
||||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
|
||||
save_successful = false
|
||||
if column.data_type == 'RepositoryListValue'
|
||||
return if value == '-1'
|
||||
# check if item exists else revert the transaction
|
||||
list_item = RepositoryListItem.where(repository_column: column)
|
||||
.find(value)
|
||||
cell_value = RepositoryListValue.new(
|
||||
repository_list_item_id: list_item.id,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
save_successful = list_item && cell_value.save
|
||||
elsif column.data_type == 'RepositoryAssetValue'
|
||||
return if value.blank?
|
||||
asset = Asset.new(file: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
team: current_team)
|
||||
if asset.save
|
||||
asset.post_process_file(current_team)
|
||||
else
|
||||
errors[:repository_cells] << {
|
||||
"#{column.id}": { data: asset.errors.messages[:file].first }
|
||||
}
|
||||
end
|
||||
cell_value = RepositoryAssetValue.new(
|
||||
asset: asset,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
save_successful = cell_value.save
|
||||
render json: { id: @record.id, flash: t('repositories.update.success_flash',
|
||||
record: escape_input(@record.name),
|
||||
repository: escape_input(@repository.name)) },
|
||||
status: :ok
|
||||
else
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
if (save_successful = cell_value.save)
|
||||
record_annotation_notification(record,
|
||||
cell_value.repository_cell)
|
||||
end
|
||||
end
|
||||
|
||||
unless save_successful
|
||||
errors[:repository_cells] << {
|
||||
"#{column.id}": cell_value.errors.messages
|
||||
}
|
||||
render json: row_update.errors, status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -377,14 +219,6 @@ class RepositoryRowsController < ApplicationController
|
|||
render_403 unless can_delete_repository_rows?(@repository)
|
||||
end
|
||||
|
||||
def record_params
|
||||
params.permit(:repository_row_name).to_h
|
||||
end
|
||||
|
||||
def cell_params
|
||||
params.permit(repository_cells: {}).to_h[:repository_cells]
|
||||
end
|
||||
|
||||
def remove_file_columns_params
|
||||
JSON.parse(params.fetch(:remove_file_columns) { '[]' })
|
||||
end
|
||||
|
@ -456,6 +290,10 @@ class RepositoryRowsController < ApplicationController
|
|||
collection
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.permit(repository_row: {}, repository_cells: {}).to_h
|
||||
end
|
||||
|
||||
def log_activity(type_of, repository_row)
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
|
|
|
@ -42,7 +42,7 @@ module FileIconsHelper
|
|||
image_link = Extends::FILE_ICON_MAPPINGS[file_ext] if Extends::FILE_ICON_MAPPINGS[file_ext]
|
||||
|
||||
if image_link
|
||||
image_tag image_link
|
||||
ActionController::Base.helpers.image_tag(image_link, class: 'image-icon')
|
||||
else
|
||||
''
|
||||
end
|
||||
|
@ -95,4 +95,12 @@ module FileIconsHelper
|
|||
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
end
|
||||
end
|
||||
|
||||
def file_extension_icon_html(asset)
|
||||
html = file_extension_icon(asset)
|
||||
html = "<i class='fas #{file_fa_icon_class(asset)}'></i>" if html.blank?
|
||||
html
|
||||
end
|
||||
|
||||
module_function :file_extension_icon_html, :file_extension_icon, :file_fa_icon_class
|
||||
end
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumnsHelper
|
||||
def form_url(repository, column)
|
||||
return repository_repository_columns_path(repository) if column.new_record?
|
||||
repository_repository_column_path(repository, column)
|
||||
end
|
||||
|
||||
def disabled?(column, type)
|
||||
return false if column.new_record?
|
||||
column.data_type != type
|
||||
end
|
||||
|
||||
def checked?(column, type)
|
||||
return true if column.new_record? && type == 'RepositoryTextValue'
|
||||
return true if column.data_type == type
|
||||
def defined_delimiters_options
|
||||
(%i(auto) + Constants::REPOSITORY_LIST_ITEMS_DELIMITERS_MAP.keys)
|
||||
.map { |e| Hash[t('libraries.manange_modal_column.list_type.delimiters.' + e.to_s), e] }
|
||||
.inject(:merge)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,58 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatableHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
def prepare_row_columns(repository_rows,
|
||||
repository,
|
||||
columns_mappings,
|
||||
team,
|
||||
_team,
|
||||
assigned_rows)
|
||||
parsed_records = []
|
||||
repository_rows.each do |record|
|
||||
includes_json = { repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES }
|
||||
|
||||
repository_rows.includes(includes_json).each do |record|
|
||||
row = {
|
||||
'DT_RowId': record.id,
|
||||
'1': assigned_row(record, assigned_rows),
|
||||
'2': record.id,
|
||||
'3': escape_input(record.name),
|
||||
'4': I18n.l(record.created_at, format: :full),
|
||||
'5': escape_input(record.created_by.full_name),
|
||||
'recordEditUrl': Rails.application.routes.url_helpers
|
||||
.edit_repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordUpdateUrl': Rails.application.routes.url_helpers
|
||||
.repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordInfoUrl': Rails.application.routes.url_helpers
|
||||
.repository_row_path(record.id)
|
||||
}
|
||||
'DT_RowId': record.id,
|
||||
'1': assigned_row(record, assigned_rows),
|
||||
'2': record.id,
|
||||
'3': escape_input(record.name),
|
||||
'4': I18n.l(record.created_at, format: :full),
|
||||
'5': escape_input(record.created_by.full_name),
|
||||
'recordEditUrl': Rails.application.routes.url_helpers
|
||||
.edit_repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordUpdateUrl': Rails.application.routes.url_helpers
|
||||
.repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordInfoUrl': Rails.application.routes.url_helpers
|
||||
.repository_row_path(record.id)
|
||||
}
|
||||
|
||||
# Add custom columns
|
||||
record.repository_cells.each do |cell|
|
||||
row[columns_mappings[cell.repository_column.id]] =
|
||||
display_cell_value(cell, team)
|
||||
display_cell_value(cell)
|
||||
end
|
||||
parsed_records << row
|
||||
end
|
||||
parsed_records
|
||||
end
|
||||
|
||||
def display_cell_value(cell, team)
|
||||
if cell.value_type == 'RepositoryAssetValue'
|
||||
# Return simple file_name if we call this method not from controller
|
||||
return cell.value.asset.file_name unless defined?(render)
|
||||
render partial: 'shared/asset_link',
|
||||
locals: { asset: cell.value.asset, display_image_tag: false },
|
||||
formats: :html
|
||||
else
|
||||
custom_auto_link(display_tooltip(cell.value.data,
|
||||
Constants::NAME_MAX_LENGTH),
|
||||
simple_format: true,
|
||||
team: team)
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_row(record, assigned_rows)
|
||||
if assigned_rows&.include?(record)
|
||||
"<span class='circle-icon'> </span>"
|
||||
|
@ -68,18 +58,16 @@ module RepositoryDatatableHelper
|
|||
can_manage_repository_rows?(repository)
|
||||
end
|
||||
|
||||
# The order must be converted from Ruby Hash into a JS array -
|
||||
# because arrays in JS are in truth regular JS objects with indexes as keys
|
||||
def default_table_order_as_js_array
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE[:order].keys.sort.map do |k|
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE[:order][k]
|
||||
end.to_s
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE['order'].to_json
|
||||
end
|
||||
|
||||
def default_table_columns
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE[:columns].keys.sort.map do |k|
|
||||
col = Constants::REPOSITORY_TABLE_DEFAULT_STATE[:columns][k]
|
||||
col.slice(:visible, :searchable)
|
||||
end.to_json
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].to_json
|
||||
end
|
||||
|
||||
def display_cell_value(cell)
|
||||
"RepositoryDatatable::#{cell.repository_column.data_type}Serializer"
|
||||
.constantize.new(cell.value).serializable_hash
|
||||
end
|
||||
end
|
||||
|
|
1
app/javascript/packs/custom/inputmask.js
Normal file
1
app/javascript/packs/custom/inputmask.js
Normal file
|
@ -0,0 +1 @@
|
|||
require('inputmask');
|
|
@ -44,7 +44,7 @@ module SearchableModel
|
|||
"CAST(#{a} AS TEXT) #{like} :t#{i} OR "
|
||||
else
|
||||
col = options[:at_search].to_s == 'true' ? "lower(#{a})": a
|
||||
"(trim_html_tags(#{col})) #{like} :t#{i} OR "
|
||||
"(trim_html_tags((#{col})::text)) #{like} :t#{i} OR "
|
||||
end
|
||||
end
|
||||
).join[0..-5]
|
||||
|
@ -86,7 +86,7 @@ module SearchableModel
|
|||
if a == 'repository_rows.id'
|
||||
"CAST(#{a} AS TEXT) #{like} :t#{i} OR "
|
||||
else
|
||||
"(trim_html_tags(#{a})) #{like} :t#{i} OR "
|
||||
"(trim_html_tags((#{a})::text)) #{like} :t#{i} OR "
|
||||
end
|
||||
end
|
||||
).join[0..-5]
|
||||
|
|
|
@ -18,6 +18,7 @@ class Repository < ApplicationRecord
|
|||
inverse_of: :repository, dependent: :destroy
|
||||
has_many :report_elements, inverse_of: :repository, dependent: :destroy
|
||||
has_many :repository_list_items, inverse_of: :repository, dependent: :destroy
|
||||
has_many :repository_checklist_items, inverse_of: :repository, dependent: :destroy
|
||||
has_many :team_repositories, inverse_of: :repository, dependent: :destroy
|
||||
has_many :teams_shared_with, through: :team_repositories, source: :team
|
||||
|
||||
|
|
|
@ -33,8 +33,12 @@ class RepositoryAssetValue < ApplicationRecord
|
|||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
asset.file.attach(io: StringIO.new(Base64.decode64(new_data[:file_data].split(',')[1])),
|
||||
filename: new_data[:file_name])
|
||||
if new_data.is_a?(String) # assume it's a signed_id_token
|
||||
asset.file.attach(new_data)
|
||||
elsif new_data[:file_data]
|
||||
asset.file.attach(io: StringIO.new(Base64.decode64(new_data[:file_data])), filename: new_data[:file_name])
|
||||
end
|
||||
|
||||
asset.last_modified_by = user
|
||||
self.last_modified_by = user
|
||||
asset.save! && save!
|
||||
|
@ -43,15 +47,15 @@ class RepositoryAssetValue < ApplicationRecord
|
|||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
team = value.repository_cell.repository_column.repository.team
|
||||
value.asset = Asset.create!(
|
||||
created_by: value.created_by,
|
||||
last_modified_by: value.created_by,
|
||||
team: team
|
||||
)
|
||||
value.asset.file.attach(
|
||||
io: StringIO.new(Base64.decode64(payload[:file_data].split(',')[1])),
|
||||
filename: payload[:file_name]
|
||||
)
|
||||
value.asset = Asset.create!(created_by: value.created_by, last_modified_by: value.created_by, team: team)
|
||||
|
||||
if payload.is_a?(String) # assume it's a signed_id_token
|
||||
value.asset.file.attach(payload)
|
||||
elsif payload[:file_data]
|
||||
value.asset.file.attach(io: StringIO.new(Base64.decode64(payload[:file_data])), filename: payload[:file_name])
|
||||
end
|
||||
|
||||
value.asset.post_process_file(team)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryCell < ActiveRecord::Base
|
||||
class RepositoryCell < ApplicationRecord
|
||||
attr_accessor :importing
|
||||
|
||||
belongs_to :repository_row
|
||||
|
@ -14,12 +14,18 @@ class RepositoryCell < ActiveRecord::Base
|
|||
.where(repository_cells: { value_type: 'RepositoryTextValue' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id
|
||||
belongs_to :repository_number_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryNumberValue' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
belongs_to :repository_date_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateValue' })
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
belongs_to :repository_list_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
|
@ -33,10 +39,59 @@ class RepositoryCell < ActiveRecord::Base
|
|||
end),
|
||||
optional: true, foreign_key: :value_id
|
||||
|
||||
validates_inclusion_of :repository_column,
|
||||
in: (lambda do |cell|
|
||||
cell.repository_row&.repository&.repository_columns || []
|
||||
end)
|
||||
belongs_to :repository_status_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryStatusValue' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_checklist_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryChecklistValue' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_date_time_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_time_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_date_time_range_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_date_range_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
belongs_to :repository_time_range_value,
|
||||
(lambda do
|
||||
includes(:repository_cell)
|
||||
.where(repository_cells: { value_type: 'RepositoryDateTimeRangeValueBase' })
|
||||
end),
|
||||
optional: true, foreign_key: :value_id, inverse_of: :repository_cell
|
||||
|
||||
validates :repository_column,
|
||||
inclusion: { in: (lambda do |cell|
|
||||
cell.repository_row&.repository&.repository_columns || []
|
||||
end) }
|
||||
validates :repository_column, presence: true
|
||||
validate :repository_column_data_type
|
||||
validates :repository_row,
|
||||
|
@ -59,7 +114,7 @@ class RepositoryCell < ActiveRecord::Base
|
|||
private
|
||||
|
||||
def repository_column_data_type
|
||||
if !repository_column || value_type != repository_column.data_type
|
||||
if !repository_column || value.class.name != repository_column.data_type
|
||||
errors.add(:value_type, 'must match column data type')
|
||||
end
|
||||
end
|
||||
|
|
8
app/models/repository_cell_values_checklist_item.rb
Normal file
8
app/models/repository_cell_values_checklist_item.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryCellValuesChecklistItem < ApplicationRecord
|
||||
belongs_to :repository_checklist_item
|
||||
belongs_to :repository_checklist_value
|
||||
|
||||
validates :repository_checklist_item, :repository_checklist_value, presence: true
|
||||
end
|
16
app/models/repository_checklist_item.rb
Normal file
16
app/models/repository_checklist_item.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryChecklistItem < ApplicationRecord
|
||||
validates :data, presence: true,
|
||||
uniqueness: { scope: :repository_column_id, case_sensitive: false },
|
||||
length: { minimum: Constants::NAME_MIN_LENGTH,
|
||||
maximum: Constants::NAME_MAX_LENGTH }
|
||||
belongs_to :repository, inverse_of: :repository_checklist_items
|
||||
belongs_to :repository_column
|
||||
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User',
|
||||
inverse_of: :created_repository_checklist_types
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User',
|
||||
inverse_of: :modified_repository_checklist_types
|
||||
has_many :repository_cell_values_checklist_items, dependent: :destroy
|
||||
has_many :repository_checklist_values, through: :repository_cell_values_checklist_items
|
||||
end
|
48
app/models/repository_checklist_value.rb
Normal file
48
app/models/repository_checklist_value.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryChecklistValue < ApplicationRecord
|
||||
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User',
|
||||
inverse_of: :created_repository_checklist_values
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User',
|
||||
inverse_of: :modified_repository_checklist_values
|
||||
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :value
|
||||
has_many :repository_cell_values_checklist_items, dependent: :destroy
|
||||
has_many :repository_checklist_items, through: :repository_cell_values_checklist_items
|
||||
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_checklist_items.data'
|
||||
SORTABLE_VALUE_INCLUDE = { repository_checklist_value: :repository_checklist_items }.freeze
|
||||
|
||||
def formatted
|
||||
repository_checklist_items.pluck(:data).join(' | ')
|
||||
end
|
||||
|
||||
def data
|
||||
repository_checklist_items.order(data: :asc).map { |i| { value: i.id, label: i.data } }
|
||||
end
|
||||
|
||||
def data_changed?(new_data)
|
||||
JSON.parse(new_data) != repository_checklist_items.pluck(:id)
|
||||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
repository_cell_values_checklist_items.destroy_all
|
||||
repository_cell.repository_column
|
||||
.repository_checklist_items.where(id: JSON.parse(new_data)).find_each do |item|
|
||||
repository_cell_values_checklist_items.create!(repository_checklist_item: item)
|
||||
end
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.repository_cell
|
||||
.repository_column
|
||||
.repository_checklist_items.where(id: JSON.parse(payload)).find_each do |item|
|
||||
value.repository_cell_values_checklist_items.new(repository_checklist_item: item)
|
||||
end
|
||||
value
|
||||
end
|
||||
end
|
|
@ -5,29 +5,41 @@ class RepositoryColumn < ApplicationRecord
|
|||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
has_many :repository_cells, dependent: :destroy
|
||||
has_many :repository_rows, through: :repository_cells
|
||||
has_many :repository_list_items, dependent: :destroy
|
||||
has_many :repository_list_items, dependent: :destroy, index_errors: true
|
||||
has_many :repository_status_items, dependent: :destroy, index_errors: true
|
||||
has_many :repository_checklist_items, dependent: :destroy, index_errors: true
|
||||
|
||||
accepts_nested_attributes_for :repository_status_items, allow_destroy: true
|
||||
accepts_nested_attributes_for :repository_list_items, allow_destroy: true
|
||||
accepts_nested_attributes_for :repository_checklist_items, allow_destroy: true
|
||||
|
||||
enum data_type: Extends::REPOSITORY_DATA_TYPES
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :name,
|
||||
presence: true,
|
||||
length: { maximum: Constants::NAME_MAX_LENGTH },
|
||||
uniqueness: { scope: :repository_id, case_sensitive: true }
|
||||
validates :created_by, presence: true
|
||||
validates :repository, presence: true
|
||||
validates :data_type, presence: true
|
||||
validates :name, :data_type, :repository, :created_by, presence: true
|
||||
|
||||
after_create :update_repository_table_states_with_new_column
|
||||
around_destroy :update_repository_table_states_with_removed_column
|
||||
|
||||
scope :list_type, -> { where(data_type: 'RepositoryListValue') }
|
||||
scope :asset_type, -> { where(data_type: 'RepositoryAssetValue') }
|
||||
scope :status_type, -> { where(data_type: 'RepositoryStatusValue') }
|
||||
scope :checkbox_type, -> { where(data_type: 'RepositoryChecklistValue') }
|
||||
|
||||
def self.name_like(query)
|
||||
where('repository_columns.name ILIKE ?', "%#{query}%")
|
||||
end
|
||||
|
||||
# Add enum check method with underscores (eg repository_list_value)
|
||||
data_types.each do |k, _|
|
||||
define_method "#{k.underscore}?" do
|
||||
public_send "#{k}?"
|
||||
end
|
||||
end
|
||||
|
||||
def update_repository_table_states_with_new_column
|
||||
service = RepositoryTableStateColumnUpdateService.new
|
||||
service.update_states_with_new_column(repository)
|
||||
|
@ -37,12 +49,12 @@ class RepositoryColumn < ApplicationRecord
|
|||
# Calculate old_column_index - this can only be done before
|
||||
# record is deleted when we still have its index
|
||||
old_column_index = (
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE[:length] +
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE['length'] +
|
||||
repository.repository_columns
|
||||
.order(id: :asc)
|
||||
.pluck(:id)
|
||||
.index(id)
|
||||
).to_s
|
||||
)
|
||||
|
||||
# Perform the destroy itself
|
||||
yield
|
||||
|
|
23
app/models/repository_date_range_value.rb
Normal file
23
app/models/repository_date_range_value.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateRangeValue < RepositoryDateTimeRangeValueBase
|
||||
def data_changed?(new_data)
|
||||
data = JSON.parse(new_data).symbolize_keys
|
||||
st = Time.zone.parse(data[:start_time])
|
||||
et = Time.zone.parse(data[:end_time])
|
||||
st.to_date != start_time.to_date || et.to_date != end_time.to_date
|
||||
end
|
||||
|
||||
def formatted
|
||||
super(:full_date)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
data = JSON.parse(payload).symbolize_keys
|
||||
|
||||
value = new(attributes)
|
||||
value.start_time = Time.zone.parse(data[:start_time])
|
||||
value.end_time = Time.zone.parse(data[:end_time])
|
||||
value
|
||||
end
|
||||
end
|
23
app/models/repository_date_time_range_value.rb
Normal file
23
app/models/repository_date_time_range_value.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateTimeRangeValue < RepositoryDateTimeRangeValueBase
|
||||
def data_changed?(new_data)
|
||||
data = JSON.parse(new_data).symbolize_keys
|
||||
st = Time.zone.parse(data[:start_time])
|
||||
et = Time.zone.parse(data[:end_time])
|
||||
st.to_i != start_time.to_i || et.to_i != end_time.to_i
|
||||
end
|
||||
|
||||
def formatted
|
||||
super(:full_with_comma)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
data = JSON.parse(payload).symbolize_keys
|
||||
|
||||
value = new(attributes)
|
||||
value.start_time = Time.zone.parse(data[:start_time])
|
||||
value.end_time = Time.zone.parse(data[:end_time])
|
||||
value
|
||||
end
|
||||
end
|
38
app/models/repository_date_time_range_value_base.rb
Normal file
38
app/models/repository_date_time_range_value_base.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateTimeRangeValueBase < ApplicationRecord
|
||||
self.table_name = 'repository_date_time_range_values'
|
||||
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User', optional: true,
|
||||
inverse_of: :created_repository_date_time_values
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User', optional: true,
|
||||
inverse_of: :modified_repository_date_time_values
|
||||
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :repository_date_time_value
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, :start_time, :end_time, presence: true
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_date_time_values.start_time'
|
||||
SORTABLE_VALUE_INCLUDE = :repository_date_time_range_value
|
||||
|
||||
def update_data!(new_data, user)
|
||||
data = JSON.parse(new_data).symbolize_keys
|
||||
self.start_time = Time.zone.parse(data[:start_time])
|
||||
self.end_time = Time.zone.parse(data[:end_time])
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
||||
def data
|
||||
[start_time, end_time].compact.join(' - ')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def formatted(format)
|
||||
[
|
||||
I18n.l(start_time, format: format),
|
||||
I18n.l(end_time, format: format)
|
||||
].compact.join(' - ')
|
||||
end
|
||||
end
|
18
app/models/repository_date_time_value.rb
Normal file
18
app/models/repository_date_time_value.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateTimeValue < RepositoryDateTimeValueBase
|
||||
def data_changed?(new_data)
|
||||
new_time = Time.zone.parse(new_data)
|
||||
new_time.to_i != data.to_i
|
||||
end
|
||||
|
||||
def formatted
|
||||
super(:full_with_comma)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.data = Time.zone.parse(payload)
|
||||
value
|
||||
end
|
||||
end
|
29
app/models/repository_date_time_value_base.rb
Normal file
29
app/models/repository_date_time_value_base.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateTimeValueBase < ApplicationRecord
|
||||
self.table_name = 'repository_date_time_values'
|
||||
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User', optional: true,
|
||||
inverse_of: :created_repository_date_time_values
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User', optional: true,
|
||||
inverse_of: :modified_repository_date_time_values
|
||||
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :repository_date_time_value
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, :data, presence: true
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_date_time_values.data'
|
||||
SORTABLE_VALUE_INCLUDE = :repository_date_time_value
|
||||
|
||||
def update_data!(new_data, user)
|
||||
self.data = Time.zone.parse(new_data)
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def formatted(format)
|
||||
I18n.l(data, format: format)
|
||||
end
|
||||
end
|
|
@ -1,34 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryDateValue < ApplicationRecord
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User'
|
||||
has_one :repository_cell, as: :value, dependent: :destroy
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, presence: true
|
||||
validates :data, presence: true
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_date_values.data'
|
||||
SORTABLE_VALUE_INCLUDE = :repository_date_value
|
||||
class RepositoryDateValue < RepositoryDateTimeValueBase
|
||||
def data_changed?(new_data)
|
||||
new_time = Time.zone.parse(new_data)
|
||||
new_time.to_date != data.to_date
|
||||
end
|
||||
|
||||
def formatted
|
||||
data
|
||||
end
|
||||
|
||||
def data_changed?(new_data)
|
||||
new_data != data
|
||||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
self.data = new_data
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
super(:full_date)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.data = payload
|
||||
value.data = Time.zone.parse(payload)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
|
35
app/models/repository_number_value.rb
Normal file
35
app/models/repository_number_value.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryNumberValue < ApplicationRecord
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User',
|
||||
inverse_of: :created_repository_number_values
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User',
|
||||
inverse_of: :modified_repository_number_values
|
||||
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :value
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, :data, presence: true
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_number_values.data'
|
||||
SORTABLE_VALUE_INCLUDE = :repository_number_value
|
||||
|
||||
def formatted
|
||||
data.to_s
|
||||
end
|
||||
|
||||
def data_changed?(new_data)
|
||||
new_data.to_f != data
|
||||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
self.data = new_data.to_f
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.data = payload.to_f
|
||||
value
|
||||
end
|
||||
end
|
14
app/models/repository_status_item.rb
Normal file
14
app/models/repository_status_item.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryStatusItem < ApplicationRecord
|
||||
validates :repository, :repository_column, :icon, presence: true
|
||||
validates :status, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH,
|
||||
maximum: Constants::NAME_MAX_LENGTH }
|
||||
belongs_to :repository
|
||||
belongs_to :repository_column
|
||||
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User', optional: true,
|
||||
inverse_of: :created_repository_status_types
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User', optional: true,
|
||||
inverse_of: :modified_repository_status_types
|
||||
has_many :repository_status_values, inverse_of: :repository_status_item, dependent: :delete_all
|
||||
end
|
45
app/models/repository_status_value.rb
Normal file
45
app/models/repository_status_value.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryStatusValue < ApplicationRecord
|
||||
belongs_to :repository_status_item
|
||||
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User', optional: true,
|
||||
inverse_of: :created_repository_status_value
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User', optional: true,
|
||||
inverse_of: :modified_repository_status_value
|
||||
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :value
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_status_item, presence: true
|
||||
|
||||
SORTABLE_COLUMN_NAME = 'repository_status_items.status'
|
||||
SORTABLE_VALUE_INCLUDE = { repository_status_value: :repository_status_item }.freeze
|
||||
|
||||
def formatted
|
||||
data
|
||||
end
|
||||
|
||||
def data_changed?(new_data)
|
||||
new_data.to_i != repository_status_item_id
|
||||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
self.repository_status_item_id = new_data.to_i
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
||||
def data
|
||||
return nil unless repository_status_item
|
||||
|
||||
"#{repository_status_item.icon} #{repository_status_item.status}"
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.repository_status_item = value.repository_cell
|
||||
.repository_column
|
||||
.repository_status_items
|
||||
.find(payload)
|
||||
value
|
||||
end
|
||||
end
|
24
app/models/repository_time_range_value.rb
Normal file
24
app/models/repository_time_range_value.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryTimeRangeValue < RepositoryDateTimeRangeValueBase
|
||||
def data_changed?(new_data)
|
||||
data = JSON.parse(new_data).symbolize_keys
|
||||
|
||||
st = Time.zone.parse(data[:start_time])
|
||||
et = Time.zone.parse(data[:end_time])
|
||||
st.hour != start_time.hour || et.hour != end_time.hour || st.min != start_time.min || et.min != end_time.min
|
||||
end
|
||||
|
||||
def formatted
|
||||
super(:time)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
data = JSON.parse(payload).symbolize_keys
|
||||
|
||||
value = new(attributes)
|
||||
value.start_time = Time.zone.parse(data[:start_time])
|
||||
value.end_time = Time.zone.parse(data[:end_time])
|
||||
value
|
||||
end
|
||||
end
|
18
app/models/repository_time_value.rb
Normal file
18
app/models/repository_time_value.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryTimeValue < RepositoryDateTimeValueBase
|
||||
def data_changed?(new_data)
|
||||
new_time = Time.zone.parse(new_data)
|
||||
new_time.min != data.min || new_time.hour != data.hour
|
||||
end
|
||||
|
||||
def formatted
|
||||
super(:time)
|
||||
end
|
||||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.data = Time.zone.parse(payload)
|
||||
value
|
||||
end
|
||||
end
|
|
@ -214,6 +214,66 @@ class User < ApplicationRecord
|
|||
has_many :assigned_my_module_repository_rows,
|
||||
class_name: 'MyModuleRepositoryRow',
|
||||
foreign_key: 'assigned_by_id'
|
||||
has_many :created_repository_status_types,
|
||||
class_name: 'RepositoryStatusItem',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_status_types,
|
||||
class_name: 'RepositoryStatusItem',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
has_many :created_repository_status_value,
|
||||
class_name: 'RepositoryStatusValue',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_status_value,
|
||||
class_name: 'RepositoryStatusValue',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
has_many :created_repository_date_time_values,
|
||||
class_name: 'RepositoryDateTimeValue',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_date_time_values,
|
||||
class_name: 'RepositoryDateTimeValue',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
has_many :created_repository_checklist_values,
|
||||
class_name: 'RepositoryChecklistValue',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_checklist_values,
|
||||
class_name: 'RepositoryChecklistValue',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
has_many :created_repository_checklist_types,
|
||||
class_name: 'RepositoryChecklistItem',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_checklist_types,
|
||||
class_name: 'RepositoryChecklistItem',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
has_many :created_repository_number_values,
|
||||
class_name: 'RepositoryNumberValue',
|
||||
foreign_key: 'created_by_id',
|
||||
inverse_of: :created_by,
|
||||
dependent: :nullify
|
||||
has_many :modified_repository_number_values,
|
||||
class_name: 'RepositoryNumberValue',
|
||||
foreign_key: 'last_modified_by_id',
|
||||
inverse_of: :last_modified_by,
|
||||
dependent: :nullify
|
||||
|
||||
has_many :user_notifications, inverse_of: :user
|
||||
has_many :notifications, through: :user_notifications
|
||||
|
|
|
@ -13,6 +13,14 @@ module Api
|
|||
object.data_type == 'RepositoryListValue' &&
|
||||
!instance_options[:hide_list_items]
|
||||
end)
|
||||
has_many :repository_status_items,
|
||||
key: :repository_status_items,
|
||||
serializer: InventoryStatusItemSerializer,
|
||||
class_name: 'RepositoryStatusItem',
|
||||
if: (lambda do
|
||||
object.data_type == 'RepositoryStatusValue' &&
|
||||
!instance_options[:hide_list_items]
|
||||
end)
|
||||
|
||||
def data_type
|
||||
Extends::API_REPOSITORY_DATA_TYPE_MAPPINGS[object.data_type]
|
||||
|
|
10
app/serializers/api/v1/inventory_status_item_serializer.rb
Normal file
10
app/serializers/api/v1/inventory_status_item_serializer.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class InventoryStatusItemSerializer < ActiveModel::Serializer
|
||||
type :inventory_status_items
|
||||
attributes :status, :icon
|
||||
end
|
||||
end
|
||||
end
|
27
app/serializers/repository_column_serializer.rb
Normal file
27
app/serializers/repository_column_serializer.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryColumnSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :message, :edit_html_url, :update_url, :destroy_html_url
|
||||
|
||||
def message
|
||||
if instance_options[:creating]
|
||||
I18n.t('libraries.repository_columns.create.success_flash', name: object.name)
|
||||
elsif instance_options[:editing]
|
||||
I18n.t('libraries.repository_columns.update.success_flash', name: object.name)
|
||||
end
|
||||
end
|
||||
|
||||
def edit_html_url
|
||||
edit_repository_repository_column_path(object.repository, object)
|
||||
end
|
||||
|
||||
def update_url
|
||||
repository_repository_columns_text_column_path(object.repository, object)
|
||||
end
|
||||
|
||||
def destroy_html_url
|
||||
repository_columns_destroy_html_path(object.repository, object)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryAssetValueSerializer < RepositoryBaseValueSerializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def value
|
||||
asset = object.asset
|
||||
{
|
||||
id: asset.id,
|
||||
url: rails_blob_path(asset.file, disposition: 'attachment'),
|
||||
preview_url: asset_file_preview_path(asset),
|
||||
file_name: asset.file_name,
|
||||
icon_html: FileIconsHelper.file_extension_icon_html(asset)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryBaseValueSerializer < ActiveModel::Serializer
|
||||
attributes :value, :value_type
|
||||
|
||||
def value
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def value_type
|
||||
object.class.name
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryChecklistValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
object.data
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryDateRangeValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
start_time: {
|
||||
formatted: I18n.l(object.start_time, format: :full_date),
|
||||
datetime: object.start_time.strftime('%Y/%m/%d %H:%M')
|
||||
},
|
||||
end_time: {
|
||||
formatted: I18n.l(object.end_time, format: :full_date),
|
||||
datetime: object.end_time.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryDateTimeRangeValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
start_time: {
|
||||
formatted: I18n.l(object.start_time, format: :full_with_comma),
|
||||
date_formatted: I18n.l(object.start_time, format: :full_date),
|
||||
time_formatted: I18n.l(object.start_time, format: :time),
|
||||
datetime: object.start_time.strftime('%Y/%m/%d %H:%M')
|
||||
},
|
||||
end_time: {
|
||||
formatted: I18n.l(object.end_time, format: :full_with_comma),
|
||||
date_formatted: I18n.l(object.end_time, format: :full_date),
|
||||
time_formatted: I18n.l(object.end_time, format: :time),
|
||||
datetime: object.end_time.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryDateTimeValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
formatted: I18n.l(object.data, format: :full_with_comma),
|
||||
date_formatted: I18n.l(object.data, format: :full_date),
|
||||
time_formatted: I18n.l(object.data, format: :time),
|
||||
datetime: object.data.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryDateValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
formatted: I18n.l(object.data, format: :full_date),
|
||||
datetime: object.data.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryListValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
id: object.repository_list_item.id,
|
||||
text: object.data
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryNumberValueSerializer < RepositoryBaseValueSerializer
|
||||
attributes :value_decimals
|
||||
|
||||
def value
|
||||
object.data
|
||||
end
|
||||
|
||||
def value_decimals
|
||||
object.repository_cell
|
||||
.repository_column
|
||||
.metadata
|
||||
.fetch('decimals') { Constants::REPOSITORY_NUMBER_TYPE_DEFAULT_DECIMALS }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryStatusValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
id: object.repository_status_item.id,
|
||||
icon: object.repository_status_item.icon,
|
||||
status: object.repository_status_item.status
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryTextValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
object.data
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryTimeRangeValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
start_time: {
|
||||
formatted: I18n.l(object.start_time, format: :time),
|
||||
datetime: object.start_time.strftime('%Y/%m/%d %H:%M')
|
||||
},
|
||||
end_time: {
|
||||
formatted: I18n.l(object.end_time, format: :time),
|
||||
datetime: object.end_time.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryDatatable
|
||||
class RepositoryTimeValueSerializer < RepositoryBaseValueSerializer
|
||||
def value
|
||||
{
|
||||
formatted: I18n.l(object.data, format: :time),
|
||||
datetime: object.data.strftime('%Y/%m/%d %H:%M')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -140,14 +140,17 @@ module ModelExporters
|
|||
{
|
||||
repository_cell: cell,
|
||||
repository_value: cell.value,
|
||||
repository_value_asset: get_cell_value_asset(cell)
|
||||
repository_value_asset: get_cell_value_asset(cell),
|
||||
repository_value_checklist: get_cell_value_checklist(cell)
|
||||
}
|
||||
end
|
||||
|
||||
def repository_column(column)
|
||||
{
|
||||
repository_column: column,
|
||||
repository_list_items: column.repository_list_items
|
||||
repository_list_items: column.repository_list_items,
|
||||
repository_checklist_items: column.repository_checklist_items,
|
||||
repository_status_items: column.repository_status_items
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -167,5 +170,11 @@ module ModelExporters
|
|||
asset_blob: cell.value.asset.blob
|
||||
}
|
||||
end
|
||||
|
||||
def get_cell_value_checklist(cell)
|
||||
return unless cell.value_type == 'RepositoryChecklistValue'
|
||||
|
||||
cell.value.repository_cell_values_checklist_items
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,6 @@ class Reports::Docx
|
|||
include InputSanitizeHelper
|
||||
include TeamsHelper
|
||||
include GlobalActivitiesHelper
|
||||
include RepositoryDatatableHelper
|
||||
|
||||
Dir[File.join(File.dirname(__FILE__), 'docx') + '**/*.rb'].each do |file|
|
||||
include_module = File.basename(file).gsub('.rb', '').split('_').map(&:capitalize).join
|
||||
|
@ -35,7 +34,6 @@ class Reports::Docx
|
|||
@docx
|
||||
end
|
||||
|
||||
|
||||
def self.link_prepare(scinote_url, link)
|
||||
link[0] == '/' ? scinote_url + link : link
|
||||
end
|
||||
|
|
|
@ -2,45 +2,15 @@
|
|||
|
||||
module Reports::Docx::DrawMyModuleRepository
|
||||
def draw_my_module_repository(subject)
|
||||
my_module = MyModule.find_by_id(subject['id']['my_module_id'])
|
||||
my_module = MyModule.find_by(id: subject['id']['my_module_id'])
|
||||
return unless my_module
|
||||
|
||||
repository_data = my_module.repository_json(subject['id']['repository_id'], subject['sort_order'], @user)
|
||||
repository_id = subject['id']['repository_id']
|
||||
repository_data = my_module.repository_json(repository_id, subject['sort_order'], @user)
|
||||
return false unless repository_data[:data].assigned_rows.count.positive?
|
||||
|
||||
records = repository_data[:data]
|
||||
assigned_rows = records.assigned_rows
|
||||
columns_mappings = records.mappings
|
||||
repository = ::Repository.find_by_id(subject['id']['repository_id'])
|
||||
repository_rows = records.repository_rows
|
||||
.preload(
|
||||
:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: :value
|
||||
)
|
||||
data = prepare_row_columns(repository_rows,
|
||||
repository,
|
||||
columns_mappings,
|
||||
repository.team,
|
||||
assigned_rows)
|
||||
|
||||
data.map! do |row|
|
||||
row.select do |key, _value|
|
||||
true if Float(key.to_s) > 1
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
table = []
|
||||
data.each do |row|
|
||||
new_row = Array.new(repository_data[:headers].length)
|
||||
row.each do |key, value|
|
||||
new_row[(key.to_s.to_i - 2)] = Sanitize.clean(value)
|
||||
end
|
||||
table.push(new_row)
|
||||
end
|
||||
table.unshift(repository_data[:headers])
|
||||
repository = ::Repository.find(repository_id)
|
||||
table = prepare_row_columns(repository_data)
|
||||
|
||||
@docx.p
|
||||
@docx.p I18n.t('projects.reports.elements.module_repository.name',
|
||||
|
|
31
app/services/reports/docx/repository_helper.rb
Normal file
31
app/services/reports/docx/repository_helper.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Reports::Docx::RepositoryHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
def prepare_row_columns(repository_data)
|
||||
result = [repository_data[:headers]]
|
||||
repository_data[:data].repository_rows.each do |record|
|
||||
row = []
|
||||
row.push(record.id)
|
||||
row.push(escape_input(record.name))
|
||||
row.push(I18n.l(record.created_at, format: :full))
|
||||
row.push(escape_input(record.created_by.full_name))
|
||||
|
||||
cell_values = {}
|
||||
custom_cells = record.repository_cells
|
||||
custom_cells.each do |cell|
|
||||
cell_values[cell.repository_column_id] = cell.value.formatted
|
||||
end
|
||||
|
||||
repository_data[:data].mappings.each do |column_id, _position|
|
||||
value = cell_values[column_id]
|
||||
row.push(value)
|
||||
end
|
||||
|
||||
result.push(row)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
|
@ -57,7 +57,7 @@ module RepositoryActions
|
|||
|
||||
def duplicate_repository_date_value
|
||||
old_value = @cell.value
|
||||
RepositoryDateValue.create(
|
||||
RepositoryDateTimeValue.create(
|
||||
old_value.attributes.merge(
|
||||
id: nil, created_by: @user, last_modified_by: @user,
|
||||
repository_cell_attributes: {
|
||||
|
|
53
app/services/repository_columns/column_service.rb
Normal file
53
app/services/repository_columns/column_service.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class ColumnService
|
||||
extend Service
|
||||
|
||||
attr_reader :errors, :column
|
||||
|
||||
def initialize(user:, repository:, column_name:, team:)
|
||||
@user = user
|
||||
@repository = repository
|
||||
@column_name = column_name
|
||||
@team = team
|
||||
@errors = {}
|
||||
@column = nil
|
||||
end
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
unless @user && @repository
|
||||
@errors[:invalid_arguments] =
|
||||
{ 'user': @user,
|
||||
'repository': @repository }
|
||||
.map do |key, value|
|
||||
"Can't find #{key.capitalize}" if value.nil?
|
||||
end.compact
|
||||
end
|
||||
|
||||
succeed?
|
||||
end
|
||||
|
||||
def log_activity(type)
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type,
|
||||
owner: @user,
|
||||
subject: @repository,
|
||||
team: @team,
|
||||
message_items: {
|
||||
repository_column: @column.id,
|
||||
repository: @repository.id
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
43
app/services/repository_columns/create_column_service.rb
Normal file
43
app/services/repository_columns/create_column_service.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RepositoryColumns
|
||||
class CreateColumnService < RepositoryColumns::ColumnService
|
||||
def initialize(user:, repository:, params:, team:, column_type:)
|
||||
super(user: user, repository: repository, team: team, column_name: params[:name])
|
||||
@column_type = column_type
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
return self unless valid?
|
||||
|
||||
@column = RepositoryColumn.new(column_attributes)
|
||||
|
||||
if @column.save
|
||||
log_activity(:create_column_inventory)
|
||||
else
|
||||
errors[:repository_column] = @column.errors.messages
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_attributes
|
||||
@params[:repository_status_items_attributes]&.map do |m|
|
||||
m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
|
||||
end
|
||||
|
||||
@params[:repository_list_items_attributes]&.map do |m|
|
||||
m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
|
||||
end
|
||||
|
||||
@params[:repository_checklist_items_attributes]&.map do |m|
|
||||
m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
|
||||
end
|
||||
|
||||
@params.merge(repository_id: @repository.id, created_by_id: @user.id, data_type: @column_type)
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue