From 7e2ac60a9d5c2aaa625d5a525e2b11493c332083 Mon Sep 17 00:00:00 2001 From: Bilel Kihal <61744974+Bilelkihal@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:59:32 +0100 Subject: [PATCH] Feature: Update ontologies selector component (#508) * init ontologies selector design * ontologies selector options * display ontologies in ontologies selector * finalize ontologies selector design * put ontologies selector in the modal component * fix ontologies result display in ontologies selector * Make ontologies selector search bar work * show ontologies results count in ontologies selector * make groups filter work in ontologies selector * make filter by categories work in ontologies selector * add ontologies selector loading animation * make submission related filters work for ontologies selector * make select all work in ontologies selector * make retired ontologies and views filters work in ontologies selector * fix ontologies style in ontologies selector * add unselect all in ontologies selector * show tab selected checks count in ontologies selector * Clean ontologies selector js controller code * Add loading animation to ontologies selector * make ontology selection work in ontologies selector * add clear selection to ontologies selector * make ontologies selector component * remove duplicated cross icon in ontologies selector * clean ontologies controller code * use rails cache in ontologies selector * replace use cases of the old ontologies selector * delete old ontologies selector code * convert ontologies selector ui component into a helper * replace the usages of the ontologies selector ui component by the ontologies selector helper * replace 'selector' by 'ontologies_selector' in ontologies controller * add internationalisation to ontologies selector * remove the usage of static cache in ontologies selector * extract save and cancel buttons to a reusable helper in ontologies selector * undo extract save and cancel buttons to a reusable helper * add another type to the loader component that uses the style of the three dots * don't use display all in submissions fetching to make it faster in ontologies controller * remove ontology_picker_single code and replace its usages * give type argument in loader component a more significant value * replace ontologies selector text input by text input helper * replace ontologies selector chips components by chips components helpers * extract save and cancel buttons to helpers in the ontologies selector * fix ontologies selector name fild in search annotator and recommender page * fix text input helper label --------- Co-authored-by: Syphax bouazzouni --- app/assets/images/x.svg | 6 +- app/assets/stylesheets/annotator.scss | 7 +- app/assets/stylesheets/bioportal.scss | 23 +- app/assets/stylesheets/components/index.scss | 4 +- app/assets/stylesheets/components/loader.scss | 12 + .../components/ontologies_selector.scss | 137 +++++++ app/components/chips_component.rb | 3 +- .../chips_component/chips_component.html.haml | 5 +- app/components/input/text_input_component.rb | 4 +- app/components/loader_component.rb | 27 +- .../turbo_frame_component.html.haml | 2 +- app/controllers/ontologies_controller.rb | 52 +++ app/helpers/application_helper.rb | 80 ++-- app/helpers/inputs_helper.rb | 6 +- app/helpers/ontologies_helper.rb | 4 + app/helpers/submission_inputs_helper.rb | 2 +- app/javascript/controllers/index.js | 5 +- .../ontologies_selector_controller.js | 72 ++++ app/views/annotator/index.html.haml | 15 +- app/views/home/index.html.haml | 6 +- .../landscape/_fair_score_landscape.html.haml | 2 +- .../ontologies_selector.html.haml | 73 ++++ .../ontologies_selector_results.html.haml | 19 + .../_metadata_tab.html.haml | 3 +- app/views/projects/_form.html.haml | 3 +- app/views/recommender/index.html.haml | 3 +- app/views/search/index.html.haml | 5 +- app/views/shared/_ontology_picker.html.erb | 55 --- .../shared/_ontology_picker_advanced.html.erb | 384 ------------------ .../shared/_ontology_picker_single.html.erb | 30 -- app/views/users/show.html.haml | 3 +- config/locales/en.yml | 20 + config/locales/fr.yml | 19 + config/routes.rb | 3 +- 34 files changed, 510 insertions(+), 584 deletions(-) create mode 100644 app/assets/stylesheets/components/loader.scss create mode 100644 app/assets/stylesheets/components/ontologies_selector.scss create mode 100644 app/javascript/controllers/ontologies_selector_controller.js create mode 100644 app/views/ontologies/ontologies_selector/ontologies_selector.html.haml create mode 100644 app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml delete mode 100644 app/views/shared/_ontology_picker.html.erb delete mode 100644 app/views/shared/_ontology_picker_advanced.html.erb delete mode 100644 app/views/shared/_ontology_picker_single.html.erb diff --git a/app/assets/images/x.svg b/app/assets/images/x.svg index 6be564a08..90be2fe0b 100644 --- a/app/assets/images/x.svg +++ b/app/assets/images/x.svg @@ -1,3 +1,3 @@ - - - \ No newline at end of file + + + diff --git a/app/assets/stylesheets/annotator.scss b/app/assets/stylesheets/annotator.scss index ce86e5fc5..1b45ff1fd 100644 --- a/app/assets/stylesheets/annotator.scss +++ b/app/assets/stylesheets/annotator.scss @@ -46,7 +46,7 @@ .annotator-page-text-area .insert-sample-text-button{ display: flex; justify-content: end; - padding: 20px; + padding: 10px 20px 20px 20px; } .annotator-page-text-area .insert-sample-text-button .button{ display: flex; @@ -188,7 +188,7 @@ display: flex; white-space: nowrap; } -.annotation-parent div,a{ +.annotation-parent div, .annotation-parent a{ margin-right: 11px; } .annotation-parent .text{ @@ -198,6 +198,9 @@ .annotation-parent a{ text-decoration: underline !important; } +.annotator-page-options .prefrences{ + margin-top: 10px; +} .annotation-parent .level{ font-weight: 600; diff --git a/app/assets/stylesheets/bioportal.scss b/app/assets/stylesheets/bioportal.scss index 7b02ab3f7..abe871771 100644 --- a/app/assets/stylesheets/bioportal.scss +++ b/app/assets/stylesheets/bioportal.scss @@ -676,6 +676,8 @@ div.tree_error { a.tree-link { display: inline-block; padding: 5px; + word-break: break-all; + text-wrap: wrap; color: var(--primary-color) !important; } @@ -1063,27 +1065,6 @@ button.fg-button-menu, button.fg-button { border: none; } -#ontology_picker_container div.chzn-container { - width: 432px !important; -} - -#ontology_picker_container div.chzn-container-active ul.chzn-choices li.search-field input { - font-style: normal !important; -} - -#ontology_picker_container div.chzn-container ul.chzn-choices { - max-height: 110px; - overflow: auto !important; -} - -#ontology_picker_container div.chzn-container li.search-field input { - font-style: oblique; -} - -#ontology_picker_container div.chzn-drop { - display: none; -} - .no_right_border { border-right: none !important; } diff --git a/app/assets/stylesheets/components/index.scss b/app/assets/stylesheets/components/index.scss index a976b7454..a93e389b1 100644 --- a/app/assets/stylesheets/components/index.scss +++ b/app/assets/stylesheets/components/index.scss @@ -30,4 +30,6 @@ @import "radio"; @import "progress_bar"; @import "search_result"; -@import "range_slider"; \ No newline at end of file +@import "range_slider"; +@import "ontologies_selector"; +@import "loader"; \ No newline at end of file diff --git a/app/assets/stylesheets/components/loader.scss b/app/assets/stylesheets/components/loader.scss new file mode 100644 index 000000000..2392bba20 --- /dev/null +++ b/app/assets/stylesheets/components/loader.scss @@ -0,0 +1,12 @@ +.loader-component{ + display: flex; + justify-content: center; +} + +.lds-ellipsis.loader-component{ + transform: scale(1); +} + +.lds-ellipsis.loader-component div{ + background: var(--primary-color); +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/ontologies_selector.scss b/app/assets/stylesheets/components/ontologies_selector.scss new file mode 100644 index 000000000..3b431afa5 --- /dev/null +++ b/app/assets/stylesheets/components/ontologies_selector.scss @@ -0,0 +1,137 @@ +.ontologies-selector-container{ + width: 800px; + padding: 0 10px; + +} +.ontologies-selector-input{ + position: relative; + padding-bottom: 70px; +} +.ontologies-selector-input input{ + width: 100%; + font-size: 15px; + padding: 15px 20px; + border-radius: 8px; + border: 1px solid #DCDCDC; + outline: none; + position: absolute; +} +.ontologies-selector-input input:focus{ + border: 1px solid #a6a6a6; +} +.ontologies-selector-input svg{ + position: absolute; + top: 17px; + right: 20px; +} +.ontologies-selector-input svg path{ + fill: #a6a6a6; +} + +.ontologies-selector-options .nav-item{ + margin-right: 0px !important; +} +.ontologies-selector-options .tab-items{ + width: 100%; + justify-content: space-between; +} +.ontologies-selector-options .tab-link{ + padding: 0 10px; +} +.ontologies-selector-options .chips{ + margin-top: 10px; + display: flex; + flex-wrap: wrap; +} +.horizontal-line{ + margin: 10px 0; + width: 100%; + height: 1px; + background-color: #d7d7d7; + border-radius: 100%; +} + +.ontologies-selector-results .chips-container div label > span{ + font-size: 16px; + padding: 10px 15px; +} +.ontologies-selector-results label{ + width: 100% +} +.ontologies-selector-results .chips-container > div{ + margin-right: 0; +} +.ontologies-selector-results .ontologies svg{ + display: none !important; +} +.ontologies-selector-results .ontologies{ + max-height: 300px; + overflow-y: scroll; +} +.save-cancel-buttons{ + display: flex; + justify-content: center; + margin-top: 20px; +} +.ontologies-selector-results .button{ + width: 200px; +} +.ontologies-selector-results .button + .button{ + margin-left: 20px +} +.switch-filters { + display: flex; +} +.switch-filters > div{ + display: flex; + padding: 15px 20px; + border: 1px solid #d7d7d7; + border-radius: 8px; + margin-bottom: 20px; + color: #666666; +} +.switch-filters > div + div{ + margin-left: 20px; +} + +.switch-filters .text{ + margin-right: 20px +} + +.ontologies-selector-results .results-number{ + color: #A1A1A1; + margin: 10px 0; +} +.ontologies-selector-results .results-number span{ + color: var(--primary-color); +} +.ontologies-selector-button{ + display: flex; + justify-content: end; + align-items: center; +} +.ontologies-selector-button .clear-selection{ + margin-top: 5px; + font-size: 15px; + color: var(--primary-color); + margin-right: 20px; + cursor: pointer; +} +.ontologies-selector-button a{ + color: var(--primary-color) !important; + margin-top: 5px; + font-size: 15px; +} +.ontologies-selector-button a svg{ + display: none; +} +.ontologies-selector-button a span{ + display: none; +} + +.ontologies-selector-results .select-all{ + cursor: pointer; +} +.ontologies-selector-button a{ + margin-right: 0; +} \ No newline at end of file diff --git a/app/components/chips_component.rb b/app/components/chips_component.rb index 9a2a4f009..624681f81 100644 --- a/app/components/chips_component.rb +++ b/app/components/chips_component.rb @@ -1,12 +1,13 @@ class ChipsComponent < ViewComponent::Base renders_one :count - def initialize(id:nil, name:, label: nil, value: nil, checked: false) + def initialize(id:nil, name:, label: nil, value: nil, checked: false, tooltip: nil) @id = id || name @name = name @value = value || 'true' @checked = checked @label = label || @value + @tooltip = tooltip end def checked? diff --git a/app/components/chips_component/chips_component.html.haml b/app/components/chips_component/chips_component.html.haml index e5ef551b6..f08d5a33d 100644 --- a/app/components/chips_component/chips_component.html.haml +++ b/app/components/chips_component/chips_component.html.haml @@ -1,10 +1,9 @@ -.chips-container{class: @disabled ? 'disabled' : ''} +.chips-container{class: @disabled ? 'disabled' : '', 'data-controller': 'tooltip', title: @tooltip} %div %label{:for => "chips-#{@id}-check"} %input{:id => "chips-#{@id}-check", :name => @name, :type => "checkbox", :value => @value, checked: checked?, disabled: @disabled} %span - %svg.chips-check-icon{:fill => "none", :height => "8", :viewbox => "0 0 10 8", :width => "10", :xmlns => "http://www.w3.org/2000/svg"} - %path{:d => "M9.76764 0.232287C9.45824 -0.0775267 8.95582 -0.0773313 8.646 0.232287L3.59787 5.28062L1.35419 3.03696C1.04438 2.72714 0.542174 2.72714 0.23236 3.03696C-0.0774534 3.34677 -0.0774534 3.84897 0.23236 4.15879L3.03684 6.96326C3.19165 7.11807 3.39464 7.19567 3.59765 7.19567C3.80067 7.19567 4.00386 7.11827 4.15867 6.96326L9.76764 1.3541C10.0775 1.0445 10.0775 0.542081 9.76764 0.232287Z"} + = inline_svg_tag 'check.svg', class: 'chips-check-icon' %div = @label = count diff --git a/app/components/input/text_input_component.rb b/app/components/input/text_input_component.rb index aad3006da..6e144d351 100644 --- a/app/components/input/text_input_component.rb +++ b/app/components/input/text_input_component.rb @@ -2,12 +2,12 @@ class Input::TextInputComponent < Input::InputFieldComponent def initialize(label: '', name:, value: nil, placeholder: '', error_message: '', helper_text: '', disabled: false, id: '', data: {}) - super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text, disabled: disabled, id: id) + super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text, disabled: disabled, id: id, data: data) end def call render Input::InputFieldComponent.new(label: @label, name: @name, value: @value, placeholder: @placeholder, error_message: @error_message, helper_text: @helper_text, - type: @type, disabled: @disabled) + type: @type, disabled: @disabled, data: @data) end end diff --git a/app/components/loader_component.rb b/app/components/loader_component.rb index 4ea3970a7..5dd234067 100644 --- a/app/components/loader_component.rb +++ b/app/components/loader_component.rb @@ -3,23 +3,36 @@ class LoaderComponent < ViewComponent::Base include ActionView::Helpers::TagHelper - def initialize(small: false) + def initialize(small: false, type: nil) super @small = small + @type = type end def small_class @small ? 'spinner-border-sm' : '' end + def type + !@type.eql?('pulsing') + end + def call - content_tag(:div, class: 'd-flex align-items-center flex-column') do - content_tag(:div, class: "spinner-border #{small_class}") do - content_tag(:span) do - t('components.loading') + if type + content_tag(:div, class: 'd-flex align-items-center flex-column') do + content_tag(:div, class: "spinner-border #{small_class}") do + content_tag(:span) do + t('components.loading') + end + content_tag(:div, class: 'spinner-text my-2') do + t('components.loading') + end end - content_tag(:div, class: 'spinner-text my-2') do - t('components.loading') + end + else + content_tag(:div, class: 'loader-component') do + content_tag(:div, class: "lds-ellipsis loader-component") do + 4.times.map{content_tag(:div)}.join.html_safe end end end diff --git a/app/components/turbo_frame_component/turbo_frame_component.html.haml b/app/components/turbo_frame_component/turbo_frame_component.html.haml index 4ffcb9641..1c5d0d9f5 100644 --- a/app/components/turbo_frame_component/turbo_frame_component.html.haml +++ b/app/components/turbo_frame_component/turbo_frame_component.html.haml @@ -7,6 +7,6 @@ = loader - else %div.p-3 - = render LoaderComponent.new + = render LoaderComponent.new(type: 'pulsing') %div{'data-turbo-frame-error-target': 'errorMessage', style:'display: none; width: 100%'} = render Display::AlertComponent.new(type:'danger') \ No newline at end of file diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index c64dedd77..07f1ad9a3 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -438,6 +438,58 @@ def metrics_evolution render partial: 'ontologies/sections/metadata/metrics_evolution_graph', locals: { data: data } end + def ontologies_selector + @categories = LinkedData::Client::Models::Category.all(display_links: false, display_context: false) + @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + @filters = ontology_filters_init(@categories, @groups) + @select_id = params[:id] + render 'ontologies/ontologies_selector/ontologies_selector' , layout: false + end + + def ontologies_selector_results + @ontologies = LinkedData::Client::Models::Ontology.all(include_views: params[:showOntologyViews]) + @total_ontologies_number = @ontologies.length + @input = params[:input] || '' + @ontologies = @ontologies.select { |ontology| ontology.name.downcase.include?(@input.downcase) || ontology.acronym.downcase.include?(@input.downcase)} + + if params[:groups] + @ontologies = @ontologies.select do |ontology| + (ontology.group & params[:groups]).any? + end + end + + if params[:categories] + @ontologies = @ontologies.select do |ontology| + (ontology.hasDomain & params[:categories]).any? + end + end + + if params[:formats] || params[:naturalLanguage] || params[:formalityLevel] || params[:isOfType] || params[:showRetiredOntologies] + submissions = LinkedData::Client::Models::OntologySubmission.all({also_include_views: 'true'}) + if params[:formats] + submissions = submissions.select { |submission| params[:formats].include?(submission.hasOntologyLanguage)} + end + if params[:naturalLanguage] + submissions = submissions.select do |submission| + (submission.naturalLanguage & params[:naturalLanguage]).any? + end + end + if params[:formalityLevel] + submissions = submissions.select { |submission| params[:formalityLevel].include?(submission.hasFormalityLevel)} + end + if params[:isOfType] + submissions = submissions.select { |submission| params[:isOfType].include?(submission.isOfType)} + end + if params[:showRetiredOntologies] + submissions = submissions.reject { |submission| submission.status.eql?('retired')} + end + @ontologies = @ontologies.select do |ontology| + submissions.any? { |submission| submission.ontology.id == ontology.id } + end + end + render 'ontologies/ontologies_selector/ontologies_selector_results' + end + private def get_views(ontology) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2d9eaa26e..c725f534b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -177,60 +177,19 @@ def error_message_alert end end - - def render_advanced_picker(custom_ontologies = nil, selected_ontologies = [], groups = nil, categories= nil, align_to_dom_id = nil) - selected_ontologies ||= [] - init_ontology_picker(custom_ontologies, selected_ontologies, groups, categories) - render :partial => "shared/ontology_picker_advanced", :locals => { - :custom_ontologies => custom_ontologies, :selected_ontologies => selected_ontologies, :align_to_dom_id => align_to_dom_id - } - end - - def init_ontology_picker(ontologies = nil, selected_ontologies = [], groups = nil, categories = nil) - get_ontologies_data(ontologies) - get_groups_data(groups) - get_categories_data(categories) - # merge group and category ontologies into a json array - onts_in_gp_or_cat = @groups_map.values.flatten.to_set - onts_in_gp_or_cat.merge @categories_map.values.flatten.to_set - @onts_in_gp_or_cat_for_js = onts_in_gp_or_cat.sort.to_json - end - - def init_ontology_picker_single - get_ontologies_data - end - - def get_ontologies_data(ontologies = nil) + def onts_for_select ontologies ||= LinkedData::Client::Models::Ontology.all(include: "acronym,name") - @onts_for_select = [] - @onts_acronym_map = {} - @onts_uri2acronym_map = {} + onts_for_select = [] ontologies.each do |ont| - # TODO: ontologies parameter may be a list of ontology models (not ontology submission models): - # ont.acronym instead of ont.ontology.acronym - # ont.name instead of ont.ontology.name - # ont.id instead of ont.ontology.id - # TODO: annotator passes in 'custom_ontologies' to the ontologies parameter. next if ( ont.acronym.nil? or ont.acronym.empty? ) acronym = ont.acronym name = ont.name - #id = ont.id # ontology URI abbreviation = acronym.empty? ? "" : "(#{acronym})" ont_label = "#{name.strip} #{abbreviation}" - #@onts_for_select << [ont_label, id] # using the URI crashes the UI checkbox selection behavior. - @onts_for_select << [ont_label, acronym] - @onts_acronym_map[ont_label] = acronym - @onts_uri2acronym_map[ont.id] = acronym # required in ontologies_to_acronyms + onts_for_select << [ont_label, acronym] end - @onts_for_select.sort! { |a,b| a[0].downcase <=> b[0].downcase } - @onts_for_js = @onts_acronym_map.to_json - end - - def categories_for_select - # This method is called in the search index page. - get_ontologies_data - get_categories_data - return @categories_for_select + onts_for_select.sort! { |a,b| a[0].downcase <=> b[0].downcase } + onts_for_select end def get_categories_data(categories = nil) @@ -641,5 +600,34 @@ def list_to_string(list) list&.join(',') || '' end + def ontologies_selector(id:, label: nil, name: nil, selected: nil) + content_tag(:div) do + render(Input::SelectComponent.new(id: id, label: label, name: name, value: onts_for_select, multiple: "multiple", selected: selected)) + + content_tag(:div, class: 'ontologies-selector-button', 'data-controller': 'ontologies-selector', 'data-ontologies-selector-id-value': id) do + content_tag(:div, t('ontologies_selector.clear_selection'), class: 'clear-selection', 'data-action': 'click->ontologies-selector#clear') + + link_to_modal(t('ontologies_selector.ontologies_advanced_selection'), "/ontologies_selector?id=#{id}", data: { show_modal_title_value: t('ontologies_selector.ontologies_advanced_selection')}) + end + end + end + + def save_button_component(class_name: nil, id: , value:, data: nil) + content_tag(:div, data: data, class: class_name) do + render Buttons::RegularButtonComponent.new(id:id, value: value, variant: "primary", state: 'regular') do |btn| + btn.icon_right do + inline_svg_tag "check.svg" + end + end + end + end + + def cancel_button_component(class_name: nil, id: , value:, data: nil) + content_tag(:div, data: data, class: class_name) do + render Buttons::RegularButtonComponent.new(id:id, value: value, variant: "secondary", state: 'regular') do |btn| + btn.icon_left do + inline_svg_tag "x.svg" + end + end + end + end end diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb index 30a235817..4fc674252 100644 --- a/app/helpers/inputs_helper.rb +++ b/app/helpers/inputs_helper.rb @@ -1,10 +1,12 @@ module InputsHelper - def text_input(name:, value:, label: nil, disabled: false, help: nil, error_message: nil) + def text_input(name:, value:nil, label: nil, disabled: false, help: nil, error_message: nil, placeholder: nil, data: nil) render Input::TextInputComponent.new(label: input_label(label, name), name: name, value: value, error_message: error_message || input_error_message(name), disabled: disabled, - helper_text: help) + helper_text: help, + placeholder: placeholder, + data: data) end def select_input(name:, values:, id: nil, label: nil, selected: nil, multiple: false, help: nil, open_to_add: false, required: false, data: {}) diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index 9f3a04ab8..90ae7690b 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -676,4 +676,8 @@ def service_button(link:, title: ) def submission_languages(submission = @submission) Array(submission&.naturalLanguage).map { |natural_language| natural_language["iso639"] && natural_language.split('/').last }.compact end + + def id_to_acronym(id) + id.split('/').last + end end diff --git a/app/helpers/submission_inputs_helper.rb b/app/helpers/submission_inputs_helper.rb index 1d914b852..958f04983 100644 --- a/app/helpers/submission_inputs_helper.rb +++ b/app/helpers/submission_inputs_helper.rb @@ -233,7 +233,7 @@ def ontology_view_of_input(ontology = @ontology) end c.container do content_tag(:div) do - render partial: "shared/ontology_picker_single", locals: { placeholder: "", field_name: "viewOf", selected: ontology.viewOf } + render SelectInputComponent.new(id: 'viewOfSelect', values: onts_for_select, name: 'ontology[viewOf]', selected: ontology.viewOf) end end end diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 36467f43b..ac732c9ba 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -98,4 +98,7 @@ import AnnotatorController from "./annotator_controller" application.register('annotator', AnnotatorController) import FormUrlController from "./form_url_controller" -application.register('form-url', FormUrlController) \ No newline at end of file +application.register('form-url', FormUrlController) + +import ontologiesSelector from "./ontologies_selector_controller" +application.register("ontologies-selector", ontologiesSelector) diff --git a/app/javascript/controllers/ontologies_selector_controller.js b/app/javascript/controllers/ontologies_selector_controller.js new file mode 100644 index 000000000..32ebd13d5 --- /dev/null +++ b/app/javascript/controllers/ontologies_selector_controller.js @@ -0,0 +1,72 @@ +import {Controller} from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ['submit', 'ontology', 'table', 'exit'] + static values = { + id: String, + selectAll: String, + unselectAll: String + } + input(){ + this.submitTarget.click() + } + change(event){ + this.submitTarget.click() + this.#updateTableNumbers(event) + } + selectall(event) { + const selectText = `\n${this.selectAllValue}\n`; + const unselectText = `\n${this.unselectAllValue}\n`; + const isChecked = event.target.innerHTML === unselectText; + const newInnerHTML = isChecked ? selectText : unselectText; + for (const target of this.ontologyTargets) { + target.querySelector('input').checked = !isChecked; + } + event.target.innerHTML = newInnerHTML; + } + apply() { + const select = document.getElementById(`select_${this.idValue}`); + const tsControl = document.getElementById(`select_${this.idValue}-ts-control`); + const values = this.#selectedOntologies(this.ontologyTargets); + for (const value of values) { + tsControl.value = value; + tsControl.dispatchEvent(new Event('input', { bubbles: true })); + select.parentNode.querySelector(`div[data-value="${value}"]`).click(); + } + this.exitTarget.click(); + } + clear() { + const selectedItems = document.getElementById(`select_${this.idValue}`).parentNode.querySelectorAll('a'); + selectedItems.forEach(item => { + item.click(); + }); + } + + #selectedOntologies(ontologies) { + return ontologies + .filter(ontology => ontology.querySelector('input').checked) + .map(ontology => ontology.querySelector('input').name); + } + + #updateTableNumbers() { + const navItems = Array.from(this.tableTarget.querySelectorAll('.nav-item')); + navItems.forEach(item => this.#updateNavItemCount(item)); + } + + #updateNavItemCount(navItem) { + const tabPane = this.tableTarget.querySelector(`.tab-pane${navItem.getAttribute('data-target')}`); + const inputs = tabPane.querySelectorAll('input'); + const count = Array.from(inputs).filter(input => input.checked).length; + const itemTitleElement = navItem.querySelector('a'); + let itemTitle = itemTitleElement.innerHTML.trim(); + const regex = /\(\d+\)/; + if (itemTitle.endsWith(")")) { + itemTitleElement.innerHTML = itemTitle.replace(regex, `(${count})`); + } else { + itemTitleElement.innerHTML = `${itemTitle} (${count})`; + } + if (count === 0) { + itemTitleElement.innerHTML = itemTitle.replace(regex, '').trim(); + } + } +} \ No newline at end of file diff --git a/app/views/annotator/index.html.haml b/app/views/annotator/index.html.haml index ea5615b1a..3f38e6050 100644 --- a/app/views/annotator/index.html.haml +++ b/app/views/annotator/index.html.haml @@ -12,27 +12,24 @@ .inputs %div .annotator-page-text-area{'data-controller': 'sample-text'} - %textarea{rows: "6" , placeholder: t('annotator.input_hint'), name: "text", maxlength: "500", 'data-sample-text-target': "input", 'data-annotator-target': 'input'} + %textarea{rows: "7" , placeholder: t('annotator.input_hint'), name: "text", maxlength: "500", 'data-sample-text-target': "input", 'data-annotator-target': 'input'} = params[:text] = insert_sample_text_button(t('recommender.insert_sample_text')) .annotator-page-options .section-text = t('annotator.options') - .select-ontologies - - get_ontologies_data - = render Input::SelectComponent.new(label: t('recommender.select_ontologies'), id: 'ontologies', name: 'ontologies[]', value: @onts_for_select, multiple: "multiple", selected: params[:ontologies]&.split(',')) .prefrences - .preftitle - = t('annotator.prefrences') .chips = render(ChipsComponent.new(name: 'whole_word_only', label: t('annotator.whole_word_only'), checked: params[:whole_word_only] || true)) = render(ChipsComponent.new(name: 'longest_only', label: t('annotator.match_longest_only'), checked: params[:longest_only])) = render(ChipsComponent.new(name: 'expand_mappings', label: t('annotator.include_mappings'), checked: params[:expand_mappings])) = render(ChipsComponent.new(name: 'exclude_numbers', label: t('annotator.exclude_numbers'), checked: params[:exclude_numbers])) = render(ChipsComponent.new(name: 'exclude_synonyms', label: t('annotator.excclude_synonyms'), checked: params[:exclude_synonyms])) - = show_advanced_options_button(text: t('show_advanced_options'), init: @advanced_options_open) - = hide_advanced_options_button(text: t('hide_advanced_options'), init: @advanced_options_open) - + + .select-ontologies + = ontologies_selector(id:'annotator_page_ontologies', label: 'Select ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) + = show_advanced_options_button(text: t('show_advanced_options'), init: @advanced_options_open) + = hide_advanced_options_button(text: t('hide_advanced_options'), init: @advanced_options_open) .more-advanced-options{'data-reveal-component-target': 'item', class: "#{@advanced_options_open ? '' : 'd-none'}"} .filters_line = render Input::SelectComponent.new(label: t('annotator.select_umls_sementic_types'), id: 'umls_semantic_types', name: 'semantic_types[]', value: @semantic_types_for_select, multiple: true, selected: params[:semantic_types]&.split(',')) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 9eec0f144..39a8fabb4 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -122,7 +122,7 @@ .home-card.home-twitter-news %a.twitter-timeline{"data-height" => "360", :href => "https://twitter.com/lagroportal?ref_src=twsrc%5Etfw"} .home-twitter-loader - = render LoaderComponent.new + = render LoaderComponent.new(type: 'pulsing') %script{:async => "", :charset => "utf-8", :src => "https://platform.twitter.com/widgets.js"} .home-section @@ -182,7 +182,7 @@ %p= t('home.support_and_collaborations') %hr.home-section-line/ .home-support-items - - $HOME_PAGE_LOGOS.each do |logo| + - $HOME_PAGE_LOGOS&.each do |logo| %a{href:logo[:url], target: "_blanc"} %img{src: asset_path(logo[:img_src])} @@ -192,7 +192,7 @@ %p= t('home.ontoportal_instances') %hr.home-section-line/ .home-support-items - - $PORTALS_INSTANCES.each do |portal| + - $PORTALS_INSTANCES&.each do |portal| %div.text-center = link_to portal[:link], target: '_blank', class: 'home-logo-instances', style: "background-color: #{portal[:color]}" do = inline_svg 'logo-white.svg', width: "35", height: "26" diff --git a/app/views/landscape/_fair_score_landscape.html.haml b/app/views/landscape/_fair_score_landscape.html.haml index 309adab7b..cf965d7cb 100644 --- a/app/views/landscape/_fair_score_landscape.html.haml +++ b/app/views/landscape/_fair_score_landscape.html.haml @@ -26,7 +26,7 @@ 0 (0%) %div.p-3 %div#detailedFilter.row.text-left - = render :partial => "shared/ontology_picker", locals: {sel_text: ""} + = ontologies_selector(id:'landscape_ontologies_selector',name: 'ontologies') %div = t('landscape.fairness_interface_introduction') %div diff --git a/app/views/ontologies/ontologies_selector/ontologies_selector.html.haml b/app/views/ontologies/ontologies_selector/ontologies_selector.html.haml new file mode 100644 index 000000000..932a061cd --- /dev/null +++ b/app/views/ontologies/ontologies_selector/ontologies_selector.html.haml @@ -0,0 +1,73 @@ += render_in_modal do + = form_tag('/ontologies_selector/results', method: :get, novalidate: true, data: {turbo_frame: 'selector_results_frame'}) do + .d-flex.align-items-center.justify-content-center{'data-controller': 'ontologies-selector', 'data-ontologies-selector-id-value': @select_id, 'data-ontologies-selector-select-all-value': t('ontologies_selector.select_all'), 'data-ontologies-selector-unselect-all-value': t('ontologies_selector.unselect_all')} + .ontologies-selector-container + .ontologies-selector-input + = text_input(label: '', placeholder: t('ontologies_selector.search_hint'), name: 'input', data: {action: 'input->ontologies-selector#input'}) + = inline_svg_tag 'icons/search.svg' + .ontologies-selector-options{'data-action': 'change->ontologies-selector#change'} + .switch-filters + .show-ontology-views + .text + = t('ontologies_selector.show_ontology_view') + = render SwitchInputComponent.new(id: 'show-ontology-views', name: 'showOntologyViews') + .show-retired-ontologies + .text + = t('ontologies_selector.hide_retired_ontologies') + = render SwitchInputComponent.new(id: 'show-retired-ontologies', name: 'showRetiredOntologies') + - formats = @formats.drop(1) + %div{'data-ontologies-selector-target': 'table'} + = render TabsContainerComponent.new do |c| + = c.item(title: t('ontologies_selector.tabs_title.categories'), selected: true) + = c.item_content do + .chips.categories + - @filters[:categories][0].each do |item| + = chips_component(label: item.acronym, id: item.acronym, value: item.id, name: 'categories[]', tooltip: item.name) + = c.item(title: t('ontologies_selector.tabs_title.groups')) + = c.item_content do + .chips.groups + - @filters[:groups][0].each do |item| + + = chips_component(label: item.acronym, id: item.acronym, value: item.id, name: 'groups[]', tooltip: item.name) + + = c.item(title: t('ontologies_selector.tabs_title.format')) + = c.item_content do + .chips.format + - formats.each do |item| + = chips_component(label: item, id: item, name: 'formats[]', value: item) + + = c.item(title: t('ontologies_selector.tabs_title.natural_languages')) + = c.item_content do + .chips.natural-language + - @filters[:naturalLanguage][0].each do |item| + = chips_component(label: item["acronym"], id: item["acronym"], name: 'naturalLanguage[]', value: item['id']) + + = c.item(title: t('ontologies_selector.tabs_title.formality_levels')) + = c.item_content do + .chips + - @filters[:hasFormalityLevel][0].each do |item| + = chips_component(label: item["name"],id: item["acronym"], name: 'formalityLevel[]', value: item["id"]) + + = c.item(title: t('ontologies_selector.tabs_title.ontology_types')) + = c.item_content do + .chips + - @filters[:isOfType][0].each do |item| + = chips_component(label: item["name"],id: item["acronym"], name: 'isOfType[]', value: item["id"]) + %input.d-none{type: 'submit', 'data-ontologies-selector-target': 'submit'} + = render TurboFrameComponent.new(id: 'selector_results_frame', src: '/ontologies_selector/results') do |container| + - container.loader do + = render LoaderComponent.new(type: 'pulsing') + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml new file mode 100644 index 000000000..19b108f29 --- /dev/null +++ b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml @@ -0,0 +1,19 @@ += render TurboFrameComponent.new(id:'selector_results_frame') do |container| + .ontologies-selector-results + .horizontal-line + .results-number + = "Showing #{@ontologies.length} of #{@total_ontologies_number}" + %span.select-all{'data-action': 'click->ontologies-selector#selectall'} + = t('ontologies_selector.select_all') + .ontologies + - @ontologies.each do |ontology| + .ontology{'data-ontologies-selector-target': 'ontology'} + = chips_component(id: "selector[#{ontology.acronym}]",value: ontology.acronym, label: "#{ontology.name} (#{ontology.acronym})", name: ontology.acronym) + .horizontal-line + .save-cancel-buttons + = cancel_button_component(class_name: 'button', id:'cancel-selector', value: t('ontologies_selector.cancel'), data: {action: "click->turbo-modal#hide", 'ontologies-selector-target': 'exit'}) + = save_button_component(class_name: 'button', id:'apply-selector', value: t('ontologies_selector.apply'), data: {action: "click->ontologies-selector#apply"}) + + + - container.loader do + = render LoaderComponent.new(type: 'pulsing') \ No newline at end of file diff --git a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml index f5c80b54b..1f378ca17 100644 --- a/app/views/ontologies_metadata_curator/_metadata_tab.html.haml +++ b/app/views/ontologies_metadata_curator/_metadata_tab.html.haml @@ -3,8 +3,7 @@ = form_tag("/ontologies_metadata_curator/result", method: "post", data: { turbo: true, turbo_frame: 'selection_metadata_form'}) do %div %div.mx-1.pt-3 - - init_ontology_picker(nil, @ontologies_ids) - = select_input(label: t("ontologies_metadata_curator.ontologies"), name: "ontology[ontologyId]", values: @onts_for_select, selected: @ontologies_ids, multiple: true, data: {placeholder: t("select_ontologies")}) + = ontologies_selector(id:'metadata_curator_ontologies_selector', label: t("ontologies_metadata_curator.ontologies") ,name: 'ontology[ontologyId]', selected: @ontologies_ids) %div.d-flex.align-items-center.mb-5 %div.mx-1{style: 'width: 65%'} = submission_metadata_selector diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index cf25b6858..811865e4c 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -57,4 +57,5 @@ %div#ontology_picker_project{style: "padding-top: 2em;"} - selected_ontologies = @project.ontologyUsed && @project.ontologyUsed.map {|id| id.split('/').last } || [] - locals = { sel_text: t('projects.form.select_ontologies'), selected_ontologies: selected_ontologies, form_object: :project, form_attribute: "ontologyUsed" } - = render :partial => "shared/ontology_picker", locals: locals + = ontologies_selector(id:'projects_page_ontologies_selector' ,name: 'ontologies') + diff --git a/app/views/recommender/index.html.haml b/app/views/recommender/index.html.haml index 2ec2bdee3..254bcac81 100644 --- a/app/views/recommender/index.html.haml +++ b/app/views/recommender/index.html.haml @@ -65,8 +65,7 @@ = t('recommender.ontologies_configuration') .inputs-container .ontologies.input - - get_ontologies_data - = render Input::SelectComponent.new(label: t('recommender.select_ontologies'), id: 'ontologies', name: 'ontologies[]', value: @onts_for_select, multiple: "multiple", selected: params[:ontologies]&.split(',')) + = ontologies_selector(id:'recommender_page_ontologies', label: t('recommender.select_ontologies') ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) .maxsets.input.d-none{'data-recommender-target': 'maxset'} = render Input::NumberComponent.new(label: t('recommender.max_ont_set'), name: "max_elements_set", value: params[:max_elements_set] || 3, min: '2', max: '4', step: '1', error_message: "#{@not_valid_max_num_set ? 'Valid values are: 2, 3, 4' : ''}") .input{'data-recommender-target': 'empty'} diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 3a31832f1..fa8a42731 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -22,9 +22,8 @@ .title = t("search.advanced_options.ontologies") .field - - get_ontologies_data - = render Input::SelectComponent.new(id: 'search-ontologies', name: 'ontologies[]', value: @onts_for_select, multiple: "multiple", selected: params[:ontologies]&.split(',')) - + = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) + .right .filter-container .title diff --git a/app/views/shared/_ontology_picker.html.erb b/app/views/shared/_ontology_picker.html.erb deleted file mode 100644 index 338d03d09..000000000 --- a/app/views/shared/_ontology_picker.html.erb +++ /dev/null @@ -1,55 +0,0 @@ -<%custom_ontologies ||= LinkedData::Client::Models::Ontology.all(include: "acronym,name")%> -<%groups ||= LinkedData::Client::Models::Group.all(include: "acronym,name,ontologies")%> -<%categories ||= LinkedData::Client::Models::Category.all(include: "acronym,name,ontologies")%> -<%selected_ontologies ||= []%> -<%init_ontology_picker(custom_ontologies, selected_ontologies, groups, categories)%> -<%form_object ||= :ontology%> -<%form_attribute ||= "ontologyId"%> -<%sel_text ||= "Select ontologies"%> -<%@select_id = "#{form_object}_#{form_attribute}"%> - - - - -
-

- <%=t('select_ontologies_list')%> -

- -
- <%=select form_object, form_attribute, options_for_select(@onts_for_select, selected_ontologies), { }, :multiple => 'true', "data-placeholder".to_sym => t("select_ontologies"), :style => "width: 432px;" %> -
- -
- - <%=t("clear_selection")%> - -     - - <%=t("select_from_list")%> - -
- - <%=render_advanced_picker(custom_ontologies, selected_ontologies, groups, categories)%> -
- diff --git a/app/views/shared/_ontology_picker_advanced.html.erb b/app/views/shared/_ontology_picker_advanced.html.erb deleted file mode 100644 index dcaa32a46..000000000 --- a/app/views/shared/_ontology_picker_advanced.html.erb +++ /dev/null @@ -1,384 +0,0 @@ - - - - diff --git a/app/views/shared/_ontology_picker_single.html.erb b/app/views/shared/_ontology_picker_single.html.erb deleted file mode 100644 index 5bcba1323..000000000 --- a/app/views/shared/_ontology_picker_single.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<%custom_ontologies ||= nil%> -<%selected ||= ""%> -<%selected = selected.split("/").last if selected.start_with?("http")%> -<%init_ontology_picker(custom_ontologies, [selected])%> -<%placeholder ||= "Select an Ontology"%> -<%picker_id ||= "ontology_picker_single"%> -<%selected ||= nil%> -<%field_name ||= "ontologyId"%> -<%object_name ||= :ontology%> -<%disabled ||= nil%> - - - -
- <%= select_input(label: placeholder, name: "#{object_name}[#{field_name}]", values: @onts_for_select, selected: selected) %> -
- diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 073277e7a..4f628615d 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -83,8 +83,7 @@ #custom_ontologies_picker{style: "left: -9999px; position: absolute;"} = form_tag custom_ontologies_path(url_encode(@user.username)) do - selected = @user.customOntology.map {|o| LinkedData::Client::Models::Ontology.get(o).acronym} - - locals = { custom_ontologies: @all_ontologies, selected_ontologies: selected, sel_text: t('users.show.select_custom_semantic_resources')} - = render partial: "shared/ontology_picker", locals: locals + = ontologies_selector(id:'account_page_ontologies_selector' ,name: 'ontologies', selected: selected) = submit_tag t('users.show.save_custom_semantic_resources'), class: "link_button" .account-page-second-row .account-page-card diff --git a/config/locales/en.yml b/config/locales/en.yml index f2cd950a0..c25ea296d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1530,3 +1530,23 @@ en: name: name visits: visits + + + ontologies_selector: + clear_selection: Clear selection + ontologies_advanced_selection: Ontologies advanced selection + search_hint: Filter ontologies ... + show_ontology_view: Show ontology views + hide_retired_ontologies: Hide retired ontologies + tabs_title: + categories: Categories + groups: Groups + format: Format + natural_languages: Natural languages + formality_levels: Formality levels + ontology_types: Ontology types + select_all: select all + cancel: Cancel + apply: Apply + select_all: select all + unselect_all: unselect all \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4a459d337..5c830d544 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1506,3 +1506,22 @@ fr: name: nom visits: visites + + ontologies_selector: + clear_selection: Effacer la sélection + ontologies_advanced_selection: Sélection avancée des ontologies + search_hint: Filtrer les ontologies... + show_ontology_view: Afficher les vues d'ontologie + hide_retired_ontologies: Masquer les ontologies retirées + tabs_title: + categories: Catégories + groups: Groupes + format: Format + natural_languages: Langues naturelles + formality_levels: Niveaux de formalité + ontology_types: Types d'ontologie + select_all: Tout sélectionner + cancel: Annuler + apply: Appliquer + select_all: Tout sélectionner + unselect_all: Tout désélectionner diff --git a/config/routes.rb b/config/routes.rb index 562ed0032..cf2c1cb3f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -136,7 +136,8 @@ get '/ontologies_filter', to: 'ontologies#ontologies_filter' get '/ontologies/:acronym/properties/show', to: 'properties#show' - + get 'ontologies_selector', to: 'ontologies#ontologies_selector' + get 'ontologies_selector/results', to: 'ontologies#ontologies_selector_results' # Notes get 'ontologies/:ontology/notes/:noteid', to: 'notes#virtual_show', as: :note_virtual, noteid: /.+/ get 'ontologies/:ontology/notes', to: 'notes#virtual_show'