diff --git a/ckanext/file_upload_widget/assets/css/styles.css b/ckanext/file_upload_widget/assets/css/styles.css index 5d1e925..808675d 100644 --- a/ckanext/file_upload_widget/assets/css/styles.css +++ b/ckanext/file_upload_widget/assets/css/styles.css @@ -7,23 +7,68 @@ .file-upload-widget .fuw-main-window .fuw-or { margin: 1rem 0; } +.file-upload-widget .fuw-url-input { + width: 100%; +} +.file-upload-widget .fuw-media-input { + width: 100%; +} .file-upload-widget .fuw-media-input .search-input { margin-bottom: 1rem; position: relative; } +.file-upload-widget .fuw-media-input .search-input input { + padding-left: 2rem; +} .file-upload-widget .fuw-media-input .search-input .input-group-btn { position: absolute; - right: 10px; + left: 10px; top: 10px; } +.file-upload-widget .fuw-media-input .fuw-media-input--files { + display: flex; + flex-direction: column; + padding: 0; + border-radius: 0.5rem; + height: 250px; + overflow-y: scroll; +} +.file-upload-widget .fuw-media-input .fuw-media-input--files .files--file-item { + display: flex; + padding: 7px 15px; + gap: 1rem; +} +.file-upload-widget .fuw-media-input .fuw-media-input--files .files--file-item label { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; +} +.file-upload-widget .fuw-media-input .fuw-media-input--files .files--file-item label:after { + display: none; +} +.file-upload-widget .fuw-media-input .fuw-media-input--files .files--file-item label .highlight { + background-color: yellow; +} +.file-upload-widget .fuw-media-input .fuw-media-input--empty { + align-items: center; + color: #939393; + display: flex; + height: 250px; + flex: 1; + flex-flow: column wrap; + justify-content: center; +} .file-upload-widget .fuw-cancel-btn { - position: absolute; - top: 5px; + cursor: pointer; } .file-upload-widget .hidden { display: none; } .file-upload-widget .modal .modal-body { min-height: 145px; + display: flex; + align-items: center; + justify-content: center; } -/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlJO0VBQ0k7RUFDQTtFQUNBO0VBQ0E7O0FBRUE7RUFDSTs7QUFLSjtFQUNJO0VBQ0E7O0FBRUE7RUFDSTtFQUNBO0VBQ0E7O0FBS1o7RUFDSTtFQUNBOztBQUdKO0VBQ0k7O0FBSUE7RUFDSSIsImZpbGUiOiJzdHlsZXMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiQGltcG9ydCBcInZhcmlhYmxlc1wiO1xuQGltcG9ydCBcIm1peGluc1wiO1xuXG4uZmlsZS11cGxvYWQtd2lkZ2V0IHtcbiAgICAuZnV3LW1haW4td2luZG93IHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjtcblxuICAgICAgICAuZnV3LW9yIHtcbiAgICAgICAgICAgIG1hcmdpbjogMXJlbSAwO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLmZ1dy1tZWRpYS1pbnB1dCB7XG4gICAgICAgIC5zZWFyY2gtaW5wdXQge1xuICAgICAgICAgICAgbWFyZ2luLWJvdHRvbTogMXJlbTtcbiAgICAgICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcblxuICAgICAgICAgICAgLmlucHV0LWdyb3VwLWJ0biB7XG4gICAgICAgICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgICAgICAgIHJpZ2h0OiAxMHB4O1xuICAgICAgICAgICAgICAgIHRvcDogMTBweDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC5mdXctY2FuY2VsLWJ0biB7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgdG9wOiA1cHg7XG4gICAgfVxuXG4gICAgLmhpZGRlbiB7XG4gICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgfVxuXG4gICAgLm1vZGFsIHtcbiAgICAgICAgLm1vZGFsLWJvZHkge1xuICAgICAgICAgICAgbWluLWhlaWdodDogMTQ1cHg7XG4gICAgICAgIH1cbiAgICB9XG59XG4iXX0= */ +/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlJO0VBQ0k7RUFDQTtFQUNBO0VBQ0E7O0FBRUE7RUFDSTs7QUFJUjtFQUNJOztBQUdKO0VBQ0k7O0FBRUE7RUFDSTtFQUNBOztBQUVBO0VBQ0k7O0FBR0o7RUFDSTtFQUNBO0VBQ0E7O0FBSVI7RUFDSTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7O0FBRUE7RUFDSTtFQUNBO0VBQ0E7O0FBRUE7RUFDSTtFQUNBO0VBQ0E7RUFDQTs7QUFFQTtFQUNJOztBQUdKO0VBQ0k7O0FBTWhCO0VBQ0k7RUFDQTtFQUNBO0VBQ0E7RUFDQTtFQUNBO0VBQ0E7O0FBSVI7RUFDSTs7QUFHSjtFQUNJOztBQUlBO0VBQ0k7RUFDQTtFQUNBO0VBQ0EiLCJmaWxlIjoic3R5bGVzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgXCJ2YXJpYWJsZXNcIjtcbkBpbXBvcnQgXCJtaXhpbnNcIjtcblxuLmZpbGUtdXBsb2FkLXdpZGdldCB7XG4gICAgLmZ1dy1tYWluLXdpbmRvdyB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG5cbiAgICAgICAgLmZ1dy1vciB7XG4gICAgICAgICAgICBtYXJnaW46IDFyZW0gMDtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC5mdXctdXJsLWlucHV0IHtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgfVxuXG4gICAgLmZ1dy1tZWRpYS1pbnB1dCB7XG4gICAgICAgIHdpZHRoOiAxMDAlO1xuXG4gICAgICAgIC5zZWFyY2gtaW5wdXQge1xuICAgICAgICAgICAgbWFyZ2luLWJvdHRvbTogMXJlbTtcbiAgICAgICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcblxuICAgICAgICAgICAgaW5wdXQge1xuICAgICAgICAgICAgICAgIHBhZGRpbmctbGVmdDogMnJlbTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLmlucHV0LWdyb3VwLWJ0biB7XG4gICAgICAgICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgICAgICAgIGxlZnQ6IDEwcHg7XG4gICAgICAgICAgICAgICAgdG9wOiAxMHB4O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLmZ1dy1tZWRpYS1pbnB1dC0tZmlsZXMge1xuICAgICAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgICAgICAgICBwYWRkaW5nOiAwO1xuICAgICAgICAgICAgYm9yZGVyLXJhZGl1czogMC41cmVtO1xuICAgICAgICAgICAgaGVpZ2h0OiAyNTBweDtcbiAgICAgICAgICAgIG92ZXJmbG93LXk6IHNjcm9sbDtcblxuICAgICAgICAgICAgLmZpbGVzLS1maWxlLWl0ZW0ge1xuICAgICAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgICAgICAgICAgcGFkZGluZzogN3B4IDE1cHg7XG4gICAgICAgICAgICAgICAgZ2FwOiAxcmVtO1xuXG4gICAgICAgICAgICAgICAgbGFiZWwge1xuICAgICAgICAgICAgICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICAgICAgICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgICAgICAgICAgICAgICBnYXA6IDAuNXJlbTtcbiAgICAgICAgICAgICAgICAgICAgd2lkdGg6IDEwMCU7XG5cbiAgICAgICAgICAgICAgICAgICAgJjphZnRlciB7XG4gICAgICAgICAgICAgICAgICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgLmhpZ2hsaWdodCB7XG4gICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB5ZWxsb3c7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAuZnV3LW1lZGlhLWlucHV0LS1lbXB0eSB7XG4gICAgICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgICAgICAgY29sb3I6ICM5MzkzOTM7XG4gICAgICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICAgICAgaGVpZ2h0OiAyNTBweDtcbiAgICAgICAgICAgIGZsZXg6IDE7XG4gICAgICAgICAgICBmbGV4LWZsb3c6IGNvbHVtbiB3cmFwO1xuICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXJcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC5mdXctY2FuY2VsLWJ0biB7XG4gICAgICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICB9XG5cbiAgICAuaGlkZGVuIHtcbiAgICAgICAgZGlzcGxheTogbm9uZTtcbiAgICB9XG5cbiAgICAubW9kYWwge1xuICAgICAgICAubW9kYWwtYm9keSB7XG4gICAgICAgICAgICBtaW4taGVpZ2h0OiAxNDVweDtcbiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICAgIH1cbiAgICB9XG59XG4iXX0= */ diff --git a/ckanext/file_upload_widget/assets/js/file-upload-widget.js b/ckanext/file_upload_widget/assets/js/file-upload-widget.js index a86123c..eed2f56 100644 --- a/ckanext/file_upload_widget/assets/js/file-upload-widget.js +++ b/ckanext/file_upload_widget/assets/js/file-upload-widget.js @@ -6,7 +6,6 @@ ckan.module("file-upload-widget", function ($, _) { fileInputButton: '.btn-file-input', urlInputButton: '.btn-url-input', mediaInputButton: '.btn-media-input', - // mainWindowBlock: '.fuw-main-window', urlInputBlock: '.fuw-url-input', mediaInputBlock: '.fuw-media-input', @@ -17,7 +16,6 @@ ckan.module("file-upload-widget", function ($, _) { console.log('file-upload-widget initialized'); - this.initBtn = this.el.find(".fuw-init-btn"); this.fileInputBtn = this.el.find(this.const.fileInputButton); this.urlInputBtn = this.el.find(this.const.urlInputButton); this.mediaInputBtn = this.el.find(this.const.mediaInputButton); @@ -26,42 +24,167 @@ ckan.module("file-upload-widget", function ($, _) { this.urlWindow = this.el.find(this.const.urlInputBlock); this.mediaWindow = this.el.find(this.const.mediaInputBlock); + this.fileSearchInput = this.el.find('#fuw-media-input--search'); + this.fileSelectBtn = this.el.find('.btn-file-select'); + this.cancelFileSelectBtn = this.el.find('.btn-cancel-file-select'); + this.cancelBtn = this.el.find(this.const.cancelBtn); // Bind events - this.initBtn.on('click', this._onTriggerModal); this.fileInputBtn.on('click', this._onFileInputTriggered); this.urlInputBtn.on('click', this._onUrlInputTriggered); this.mediaInputBtn.on('click', this._onMediaInputTriggered); this.cancelBtn.on('click', this._onCancelAction); + this.fileSearchInput.on('input', this._onFileSearch); + this.el.find('li.files--file-item input').on('change', this._onFileSelect); + this.cancelFileSelectBtn.on('click', this._onCancelFileSelect); }, - _onTriggerModal: function (e) { - console.log('trigger modal'); - }, + + /** + * Handle file input trigger + * + * On file input trigger show an HTML file selector + * + * @param {Event} e + */ _onFileInputTriggered: function (e) { console.log('file input triggered'); }, - _onUrlInputTriggered: function (e) { - console.log('url input triggered'); + /** + * Handle url input trigger + * + * On url input trigger show url input block and hide main window + * + * @param {Event} e + */ + _onUrlInputTriggered: function (e) { this.urlWindow.toggle(); this.mainWindow.toggle(); this.cancelBtn.toggle(); }, - _onMediaInputTriggered: function (e) { - console.log('media input triggered'); + /** + * Handle media input trigger + * + * On media input trigger show media input block and hide main window + * + * @param {Event} e + */ + _onMediaInputTriggered: function (e) { this.mediaWindow.toggle(); this.mainWindow.toggle(); this.cancelBtn.toggle(); }, - _onCancelAction: function (e) { - console.log('cancel action'); + /** + * Handle cancel action + * + * On cancel action hide url and media input blocks and show main window + * and hide the cancel button. + * + * @param {Event} e + */ + _onCancelAction: function (e) { this.urlWindow.hide(); this.mediaWindow.hide(); this.mainWindow.toggle(); this.cancelBtn.toggle(); + }, + + /** + * Handle file search input + * + * On file search input change hide files that don't match search query + * and highlight text that matches search query. + * + * If there are no visible files show empty state message. + * + * @param {Event} e + */ + _onFileSearch: function (e) { + let search = this.fileSearchInput.val().toLowerCase(); + let isEmpty = true; + + this.mediaWindow.find('li.files--file-item').each((_, element) => { + let title = $(element).find("label span").text().trim().toLowerCase(); + + if (!!search && title.indexOf(search) === -1) { + $(element).hide(); + } else { + $(element).show(); + + isEmpty = false; + + this._highlightText(element, search); + }; + }); + + let visibleFilesNum = this.el.find("li.files--file-item:visible").length; + + if (isEmpty && !visibleFilesNum) { + this.el.find(".fuw-media-input--empty").show(); + this.el.find(".fuw-media-input--files").hide(); + } else { + this.el.find(".fuw-media-input--empty").hide(); + this.el.find(".fuw-media-input--files").show(); + } + }, + + /** + * Highlight text in file name + * + * If text is provided highlight it in file name + * If text is empty remove highlight using the original file name + * + * @param {HTMLElement} element - file li item + * @param {String} text - text to highlight + */ + _highlightText: function (element, text) { + let originalName = $(element).attr("fuw-original-file-name"); + + let highlightedName = originalName; + + if (text) { + let regex = new RegExp(text, 'gi'); // 'g' for global, 'i' for case-insensitive + + highlightedName = originalName.replace(regex, function (match) { + return "" + match + ""; + }); + } + + $(element).find("label span").html(highlightedName); + }, + + /** + * Handle file selection + * + * @param {Event} e + */ + _onFileSelect: function (e) { + let selectedFilesNum = this._countSelectedFiles(); + + this.fileSelectBtn.find("span").text(selectedFilesNum); + this.el.find(".modal-footer").toggle(!!selectedFilesNum); + }, + + /** + * Count selected files + * + * @returns {Number} - number of selected files + */ + _countSelectedFiles: function () { + return this.el.find('li.files--file-item input:checked').length; + }, + + /** + * Cancel file selection + * + * @param {Event} e + */ + _onCancelFileSelect: function (e) { + this.el.find('li.files--file-item input:checked').prop('checked', false); + this.el.find('li.files--file-item input:first').trigger('change'); } }; }); diff --git a/ckanext/file_upload_widget/templates/scheming/form_snippets/files_upload_widget.html b/ckanext/file_upload_widget/templates/scheming/form_snippets/files_upload_widget.html index a58118b..2c9ce88 100644 --- a/ckanext/file_upload_widget/templates/scheming/form_snippets/files_upload_widget.html +++ b/ckanext/file_upload_widget/templates/scheming/form_snippets/files_upload_widget.html @@ -28,7 +28,7 @@ diff --git a/ckanext/file_upload_widget/theme/styles.scss b/ckanext/file_upload_widget/theme/styles.scss index b4f85a7..f4bad4e 100644 --- a/ckanext/file_upload_widget/theme/styles.scss +++ b/ckanext/file_upload_widget/theme/styles.scss @@ -13,22 +13,71 @@ } } + .fuw-url-input { + width: 100%; + } + .fuw-media-input { + width: 100%; + .search-input { margin-bottom: 1rem; position: relative; + input { + padding-left: 2rem; + } + .input-group-btn { position: absolute; - right: 10px; + left: 10px; top: 10px; } } + + .fuw-media-input--files { + display: flex; + flex-direction: column; + padding: 0; + border-radius: 0.5rem; + height: 250px; + overflow-y: scroll; + + .files--file-item { + display: flex; + padding: 7px 15px; + gap: 1rem; + + label { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + + &:after { + display: none; + } + + .highlight { + background-color: yellow; + } + } + } + } + + .fuw-media-input--empty { + align-items: center; + color: #939393; + display: flex; + height: 250px; + flex: 1; + flex-flow: column wrap; + justify-content: center + } } .fuw-cancel-btn { - position: absolute; - top: 5px; + cursor: pointer; } .hidden { @@ -38,6 +87,9 @@ .modal { .modal-body { min-height: 145px; + display: flex; + align-items: center; + justify-content: center; } } }