diff --git a/app/assets/images/arrow-right.svg b/app/assets/images/arrow-right.svg index 21ce5092c3..07eb88ef14 100644 --- a/app/assets/images/arrow-right.svg +++ b/app/assets/images/arrow-right.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/app/assets/images/danger.svg b/app/assets/images/danger.svg new file mode 100644 index 0000000000..27f5cef638 --- /dev/null +++ b/app/assets/images/danger.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/icons/arrow-left.svg b/app/assets/images/icons/arrow-left.svg new file mode 100644 index 0000000000..b3c63be0de --- /dev/null +++ b/app/assets/images/icons/arrow-left.svg @@ -0,0 +1,4 @@ + + + diff --git a/app/assets/images/icons/white-check.svg b/app/assets/images/icons/white-check.svg new file mode 100644 index 0000000000..9ef8028d3f --- /dev/null +++ b/app/assets/images/icons/white-check.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/assets/images/info.svg b/app/assets/images/info.svg new file mode 100644 index 0000000000..8b9e031f64 --- /dev/null +++ b/app/assets/images/info.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/json.svg b/app/assets/images/json.svg index 7af4b2fb2f..e7f7e88985 100644 --- a/app/assets/images/json.svg +++ b/app/assets/images/json.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/app/assets/images/success.svg b/app/assets/images/success.svg new file mode 100644 index 0000000000..1a6d1a81e3 --- /dev/null +++ b/app/assets/images/success.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/warning.svg b/app/assets/images/warning.svg new file mode 100644 index 0000000000..0ca8db2606 --- /dev/null +++ b/app/assets/images/warning.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/white-check.svg b/app/assets/images/white-check.svg new file mode 100644 index 0000000000..6b657da4fd --- /dev/null +++ b/app/assets/images/white-check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/x.svg b/app/assets/images/x.svg new file mode 100644 index 0000000000..6be564a080 --- /dev/null +++ b/app/assets/images/x.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index 0810149530..ae2bcd71f6 100755 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -51,8 +51,8 @@ @import "components/index"; @import "account"; @import "agents"; +@import "upload_ontology"; @import "edit-ontology"; - @import "nav_bar"; @import "ontology_details_header"; @import "ontology_viewer"; diff --git a/app/assets/stylesheets/bioportal.scss b/app/assets/stylesheets/bioportal.scss index 607d236309..c3ccdf04f0 100644 --- a/app/assets/stylesheets/bioportal.scss +++ b/app/assets/stylesheets/bioportal.scss @@ -1143,6 +1143,9 @@ a.truncated_less, a.truncated_more { .highlighted .search_ontology_acronym { color: white; } +.hide { + display: none !important; +} .empty-state { display: none; diff --git a/app/assets/stylesheets/components/alert.scss b/app/assets/stylesheets/components/alert.scss new file mode 100644 index 0000000000..e68adc4862 --- /dev/null +++ b/app/assets/stylesheets/components/alert.scss @@ -0,0 +1,84 @@ +.alert-container{ + display: flex; + align-items: center; + padding: 20px; + border-radius: 5px; +} +.alert-container.alert-info-type{ + background-color: rgba(59, 130, 246, 0.1); +} +.alert-container.alert-warning-type { + background-color: rgba(234, 179, 8, 0.1); +} +.alert-container.alert-danger-type { + background-color: rgba(239, 68, 68, 0.1); +} +.alert-container.alert-success-type { + background-color: rgba(34, 197, 94, 0.1); +} + + + + + +.alert-message{ + font-size: 16px; + margin: 0 10px; + text-align: center; +} + + + +.alert-message.alert-info-type{ + color: rgb(59, 130, 246); +} +.alert-message.alert-warning-type { + color: rgb(234, 179, 8); +} +.alert-message.alert-danger-type { + color: rgb(239, 68, 68); +} +.alert-message.alert-success-type { + color: rgb(34, 197, 94); +} + + + + +.alert-icon svg{ + width: 25px; + margin: 0 !important; +} +.alert-icon.alert-info-type svg path{ + fill: rgb(59, 130, 246); +} +.alert-icon.alert-warning-type svg path { + fill: rgb(234, 179, 8); +} +.alert-icon.alert-danger-type svg path { + fill: rgb(239, 68, 68); +} +.alert-icon.alert-success-type svg path { + fill: rgb(34, 197, 94); +} + + +.alert-close svg{ + width: 25px; + cursor: pointer; +} + +.alert-close.alert-info-type svg path{ + fill:rgb(59, 130, 246); +} +.alert-close.alert-warning-type svg path { + fill: rgb(234, 179, 8); +} +.alert-close.alert-danger-type svg path { + fill: rgb(239, 68, 68); +} +.alert-close.alert-success-type svg path { + fill: rgb(34, 197, 94); +} + + diff --git a/app/assets/stylesheets/components/chips.scss b/app/assets/stylesheets/components/chips.scss index d0539b6a0e..9815dbdf1f 100644 --- a/app/assets/stylesheets/components/chips.scss +++ b/app/assets/stylesheets/components/chips.scss @@ -1,3 +1,7 @@ +.chips-container > div{ + margin-right: 10px; +} + .chips-container svg path{ fill: var(--primary-color); } @@ -7,6 +11,7 @@ display: none; } + .chips-container div label{ cursor: pointer; } @@ -20,9 +25,9 @@ background:white; border: 0.5px solid #BDBDBD; color: #a7a7a7; - padding: 3px; + padding: 8px; border-radius: 5px; - font-size: 12px; + font-size: 13px; user-select: none; } diff --git a/app/assets/stylesheets/components/index.scss b/app/assets/stylesheets/components/index.scss index 8c061af437..1f6528910a 100644 --- a/app/assets/stylesheets/components/index.scss +++ b/app/assets/stylesheets/components/index.scss @@ -24,5 +24,5 @@ @import "card"; @import "header"; @import "image"; - - +@import "alert"; +@import "progress_pages"; diff --git a/app/assets/stylesheets/components/input_field.scss b/app/assets/stylesheets/components/input_field.scss index 9a5ad99c94..f11b84188f 100644 --- a/app/assets/stylesheets/components/input_field.scss +++ b/app/assets/stylesheets/components/input_field.scss @@ -66,4 +66,20 @@ right: calc(0.75rem + 5px); top: 50%; width: 0; +} + +.ts-wrapper.multi .ts-control>div{ + border-radius: 5px; + font-size: 11px; + padding: 2px 0 4px 6px; + color: #888888; +} + +.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove{ + border-left: none; + color: #888888 !important; + margin-left: 0; +} +.ts-wrapper.plugin-remove_button .item .remove:hover{ + background: unset; } \ No newline at end of file diff --git a/app/assets/stylesheets/components/primary_button.scss b/app/assets/stylesheets/components/primary_button.scss new file mode 100644 index 0000000000..5e534fecdb --- /dev/null +++ b/app/assets/stylesheets/components/primary_button.scss @@ -0,0 +1,101 @@ +.button-container{ + width: 100%; +} +.primary-button { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 16px; + color: white; + height: 60px; + background-color: var(--primary-color); + border: none; + border-radius: 9px; + transition: background-color ease 0.3s; +} +.primary-button:hover { + background-color: var(--hover-color); + cursor: pointer; +} + + +.animation-container{ + width: 100%; + height: 60px; + font-size: 16px; + background-color: var(--hover-color); + border: none; + border-radius: 9px; + justify-content: center; + align-items: center; + display: none; + +} +.lds-ellipsis { + display: inline-block; + position: relative; + margin-top: 50px; + width: 80px; + height: 80px; + transform: scale(0.7); +} + +.lds-ellipsis div { + position: absolute; + width: 13px; + height: 13px; + border-radius: 50%; + background: white; + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} + +.lds-ellipsis div:nth-child(1) { + left: 8px; + animation: lds-ellipsis1 0.6s infinite; +} + +.lds-ellipsis div:nth-child(2) { + left: 8px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(3) { + left: 32px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(4) { + left: 56px; + animation: lds-ellipsis3 0.6s infinite; +} + +@keyframes lds-ellipsis1 { + 0% { + transform: scale(0); + } + + 100% { + transform: scale(1); + } +} + +@keyframes lds-ellipsis3 { + 0% { + transform: scale(1); + } + + 100% { + transform: scale(0); + } +} + +@keyframes lds-ellipsis2 { + 0% { + transform: translate(0, 0); + } + + 100% { + transform: translate(24px, 0); + } +} diff --git a/app/assets/stylesheets/components/progress_pages.scss b/app/assets/stylesheets/components/progress_pages.scss new file mode 100644 index 0000000000..6e3f6cff5c --- /dev/null +++ b/app/assets/stylesheets/components/progress_pages.scss @@ -0,0 +1,118 @@ +.progress-pages-container { + display: flex; + align-items: center; + margin-top: 20px; + margin-bottom: 50px; + width: 100%; +} + +.progress-pages-container hr { + flex-grow: 1; + border: 1px solid #D9D9D9; + margin: 0; +} + +.progress-pages-container hr.active { + border: 1px solid var(--primary-color); +} + +.progress-pages-container .progress-item { + position: relative; + & > div { + position: absolute; + top: -13px; + left: -13px; + display: flex; + flex-direction: column; + align-items: center; + } +} + +.progress-pages-container .progress-item:first-of-type { + & > div { + align-items: start; + left: 0; + } +} + +.progress-pages-container .progress-item:last-of-type { + & > div { + align-items: end; + left: -23px; + } +} + +.progress-pages-container .progress-item div .active:nth-child(2) { + color: var(--primary-color); + font-weight: 600; +} + +.progress-pages-container .progress-item div span:nth-child(2) { + position: absolute; + font-size: 12px; + top: 33px; + white-space: nowrap; + color: #A9A9A9; +} + +.progress-pages-container .progress-content .active:nth-child(2) { + color: var(--primary-color); + font-weight: 600; +} + +.progress-pages-container .progress-content div:nth-child(2) { + position: absolute; + right: -38px; + top: 33px; + +} + + + +.outlined-checked-circle { + border: 2px solid var(--primary-color) !important; + background-color: var(--primary-color) !important; + display: flex; + align-items: center; + justify-content: center; +} + +.outlined-active-circle { + border: 2px solid var(--primary-color) !important; +} + +.outlined-circle { + border: 1px solid #D9D9D9; + border-radius: 13px; + height: 26px; + width: 26px; + background-color: white; + +} + +.outlined-checked-circle img { + display: block !important; +} + +.outlined-active-circle img { + display: none; +} + +.outlined-circle img { + display: none; +} + + + +.progress-pages-actions { + display: flex; + justify-content: flex-end; + margin-top: 20px; +} + +.progress-pages-next-button { + margin-left: 25px; +} + + + diff --git a/app/assets/stylesheets/components/regular_button.scss b/app/assets/stylesheets/components/regular_button.scss index 9541652970..762b285b28 100644 --- a/app/assets/stylesheets/components/regular_button.scss +++ b/app/assets/stylesheets/components/regular_button.scss @@ -105,10 +105,12 @@ .left-button-icon{ margin-right: 10px; + margin-bottom: 3px; } .right-button-icon { margin-left: 10px; + margin-bottom: 3px; } .secondary-button-icon path { diff --git a/app/assets/stylesheets/upload_ontology.scss b/app/assets/stylesheets/upload_ontology.scss new file mode 100644 index 0000000000..d5026556ca --- /dev/null +++ b/app/assets/stylesheets/upload_ontology.scss @@ -0,0 +1,105 @@ +.upload-ontology-container { + display: flex; + justify-content: center; + padding: 40px 0; +} + +.upload-ontology-card { + width: 589px; + border-radius: 14px; + box-shadow: rgba(0, 0, 0, 0.05) 0px 20px 50px; + padding: 20px 40px; + +} + +.upload-ontology-center { + display: flex; + justify-content: center; + flex-direction: column; +} + +.Upload-ontology-title { + font-size: 18px; + display: flex; + font-weight: bold; + flex-direction: column; + align-items: center; +} + +.Upload-ontology-title hr { + border: 1px solid var(--primary-color); + width: 93px; + margin: 0; +} + +.upload-ontology-progress { + display: flex; + align-items: center; + margin-top: 20px; + margin-bottom: 50px; +} + + +.upload-ontology-chips-container{ + display: flex; + flex-wrap: wrap; +} + + +.hide { + display: none; +} + +.show { + display: block; +} + +.upload-ontology-desc { + font-size: 12px; + color: #777777; + margin-bottom: 23px; +} + +.upload-ontology-desc a { + text-decoration: none; + color: var(--primary-color); +} +.upload-ontology-desc a svg{ + transform: scale(1.2); +} + + +.upload-ontology-contact .add-another-contact div { + font-size: 11px; + color: #DADADA; + margin-left: 10px; + +} + +.upload-ontology-field-container .location-choice{ + display: flex; + align-items: center; + margin-bottom: 3px; +} + +.upload-ontology-field-container .location-choice .title{ + font-size: 13px; + color: black; + margin-left: 13px; + margin-bottom: 0; + cursor: pointer; +} + +.upload-ontology-field-container > div{ + font-size: 12px; + color: #666666; +} + + +.upload-ontology-input-field-container{ + margin-bottom: 10px; +} + +.upload-ontology-input-field-container .switch-filter p{ + font-size: 12px !important; +} diff --git a/app/components/alert_message_component.rb b/app/components/alert_message_component.rb deleted file mode 100644 index 09ece182b6..0000000000 --- a/app/components/alert_message_component.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class AlertMessageComponent < ViewComponent::Base - include Turbo::FramesHelper - def initialize(id: '', message: nil, type: 'info', closeable: true) - @id = id - @message = message - @type = "alert-#{type}" - @closeable = closeable - end -end diff --git a/app/components/alert_message_component/alert_message_component.html.haml b/app/components/alert_message_component/alert_message_component.html.haml deleted file mode 100644 index c32ec7d3c7..0000000000 --- a/app/components/alert_message_component/alert_message_component.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.alert.alert-dismissible.fade.show{:role => "alert", class: "#{@type}", style: "text-align: left;white-space: normal;"} - = @message || content - - - if @closeable - %button.close{"aria-label": "Close", "data-dismiss": "alert", type: "button", style: "background: transparent"} - %span{"aria-hidden" => "true"} × - - \ No newline at end of file diff --git a/app/components/buttons/primary_button_component/primary_button_component.html.haml b/app/components/buttons/primary_button_component/primary_button_component.html.haml new file mode 100644 index 0000000000..154eb2abe3 --- /dev/null +++ b/app/components/buttons/primary_button_component/primary_button_component.html.haml @@ -0,0 +1,23 @@ +- if @type == "submit" + .button-container{onclick: "displayAnimation()", id: "primary-button"} + %input.primary-button{:name => @name, :type => "submit", :value => @value, oncklick: @onclick}/ +- else + .button-container + .primary-button{:name => @name, onclick: "displayAnimation()", id: "primary-button", oncklick: @onclick} + = @value + +.animation-container#loading-animation + .lds-ellipsis + %div + %div + %div + %div + +:javascript + const button = document.getElementById("primary-button") + const loading = document.getElementById("loading-animation") + function displayAnimation(){ + console.log("working") + button.style.display = 'none'; + loading.style.display = 'flex'; + } \ No newline at end of file diff --git a/app/components/buttons/regular_button_component.rb b/app/components/buttons/regular_button_component.rb index c40a9f67b0..cb75ef32a3 100644 --- a/app/components/buttons/regular_button_component.rb +++ b/app/components/buttons/regular_button_component.rb @@ -16,7 +16,7 @@ def initialize(id: , value:, variant: "primary", color: "normal", href: "", size def button_label hide_icon_left = icon_left == nil ? "hide" : " " hide_icon_right = icon_right == nil ? "hide" : " " - content_tag(:span, icon_left, class: "#{@variant}-button-icon left-button-icon #{hide_icon_left}") + @value + content_tag(:span, icon_right, class: "#{@variant}-button-icon right-button-icon #{hide_icon_right}") + content_tag(:span, icon_left, class: "#{@variant}-button-icon left-button-icon #{hide_icon_left}") + content_tag(:div, @value) + content_tag(:span, icon_right, class: "#{@variant}-button-icon right-button-icon #{hide_icon_right}") end def button_elem diff --git a/app/components/chips_component/chips_component.html.haml b/app/components/chips_component/chips_component.html.haml index 48a0b88a4e..3191dc1d3b 100644 --- a/app/components/chips_component/chips_component.html.haml +++ b/app/components/chips_component/chips_component.html.haml @@ -5,6 +5,6 @@ %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"} - %div.ml-1 + %div = @label = count diff --git a/app/components/display/alert_component.rb b/app/components/display/alert_component.rb new file mode 100644 index 0000000000..c381f2a37e --- /dev/null +++ b/app/components/display/alert_component.rb @@ -0,0 +1,47 @@ +class Display::AlertComponent < ViewComponent::Base + def initialize(message: nil, closable: true, type: "info", auto_close_delay: nil) + @message = message + @closable = closable + @type = type + @auto_close_delay = auto_close_delay + end + + def closable? + @closable + end + + def message + @message + end + + def alert_type_class + case @type + when "info" + "alert-info-type" + when "warning" + "alert-warning-type" + when "danger" + "alert-danger-type" + when "success" + "alert-success-type" + end + end + + def alert_icon + case @type + when "info" + "info.svg" + when "warning" + "warning.svg" + when "danger" + "danger.svg" + when "success" + "success.svg" + end + end + + def auto_close? + !@auto_close_delay.nil? + end + +end \ No newline at end of file diff --git a/app/components/display/alert_component/alert_component.html.haml b/app/components/display/alert_component/alert_component.html.haml new file mode 100644 index 0000000000..d0bc0d5ce6 --- /dev/null +++ b/app/components/display/alert_component/alert_component.html.haml @@ -0,0 +1,14 @@ +.alert-container{data: {controller: "alert-component", + 'alert-component-auto-close-value': auto_close?, + 'alert-component-auto-close-after-value': @auto_close_delay, + }, + class: alert_type_class} + .alert-icon{class: alert_type_class} + = inline_svg_tag alert_icon + .alert-message{class: alert_type_class} + = message || content + - if closable? + .alert-close{"data-action":"click->alert-component#close", class: alert_type_class} + = inline_svg_tag "x.svg" + + \ No newline at end of file diff --git a/app/components/display/alert_component/alert_component_controller.js b/app/components/display/alert_component/alert_component_controller.js new file mode 100644 index 0000000000..0abf19ddae --- /dev/null +++ b/app/components/display/alert_component/alert_component_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + + static values = { + autoCloseAfter: { type: Number, default: 5000 }, + autoClose: { type: Boolean, default: false }, + } + connect(){ + if (this.autoCloseValue){ + setTimeout(() => { + this.close() + }, this.autoCloseAfterValue); + } + } + close(){ + this.element.style.display = "none" + } +} diff --git a/app/components/infinite_scroll_component/infinite_scroll_component.html.haml b/app/components/infinite_scroll_component/infinite_scroll_component.html.haml index c0fd9abac8..5bdcb976cc 100644 --- a/app/components/infinite_scroll_component/infinite_scroll_component.html.haml +++ b/app/components/infinite_scroll_component/infinite_scroll_component.html.haml @@ -12,5 +12,4 @@ Loading ... - elsif @current_page == 1 %div.text-wrap - = render AlertMessageComponent.new(id: "#{@id}_view_alert") do - = error + = render Display::AlertComponent.new { error } diff --git a/app/components/input/date_component.rb b/app/components/input/date_component.rb index fe85fa8925..2549e36709 100644 --- a/app/components/input/date_component.rb +++ b/app/components/input/date_component.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class Input::DateComponent < Input::InputFieldComponent - def initialize(label: '', name:, value: Date.today, placeholder: '', error_message: '', helper_text: '') - super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text) + def initialize(label: '', name:, value: Date.today, placeholder: '', error_message: '', helper_text: '', id: nil) + data_flat_picker = { controller: "flatpickr", flatpickr_date_format: "Y-m-d", flatpickr_alt_input: "true", flatpickr_alt_format: "F j, Y" } + super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text, data: data_flat_picker, id: id) end def call - render Input::InputFieldComponent.new(label: @label, name: @name, value: @value, placeholder: @placeholder, error_message: @error_message, helper_text: @helper_text, type: 'date') + render Input::InputFieldComponent.new(label: @label, name: @name, value: @value, placeholder: @placeholder, error_message: @error_message, helper_text: @helper_text, data: @data, type: 'date', id: @id) end end diff --git a/app/components/input/input_field_component.rb b/app/components/input/input_field_component.rb index 01ed85ef6d..d026ca0e54 100644 --- a/app/components/input/input_field_component.rb +++ b/app/components/input/input_field_component.rb @@ -1,5 +1,5 @@ class Input::InputFieldComponent < ViewComponent::Base - def initialize(label: "" , name:, value: 'Syphax', type: 'text', placeholder: "", error_message: "", helper_text: "") + def initialize(label: "" , name:, value: 'Syphax', type: 'text', placeholder: "", error_message: "", helper_text: "", disabled: false, data: nil, id: '') @label = label @name = name @placeholder = placeholder @@ -7,6 +7,9 @@ def initialize(label: "" , name:, value: 'Syphax', type: 'text', placeholder: "" @helper_text = helper_text @value = value @type = type + @disabled = disabled + @id = id + @data = data end diff --git a/app/components/input/input_field_component/input_field_component.html.haml b/app/components/input/input_field_component/input_field_component.html.haml index 0bd9f9a103..9500ba74d9 100644 --- a/app/components/input/input_field_component/input_field_component.html.haml +++ b/app/components/input/input_field_component/input_field_component.html.haml @@ -6,7 +6,7 @@ - if content = content - else - %input.input-field-component.text-input{name: @name, type: @type, placeholder: @placeholder, style: error_style, value: @value} + %input.input-field-component.text-input{class: @disabled ? '' : 'not-disabled', name: @name, type: @type, placeholder: @placeholder, style: error_style, id: @id, data: @data, value: @value, disabled: @disabled} diff --git a/app/components/input/text_input_component.rb b/app/components/input/text_input_component.rb index 7a38b3de01..d5cca42ebf 100644 --- a/app/components/input/text_input_component.rb +++ b/app/components/input/text_input_component.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true class Input::TextInputComponent < Input::InputFieldComponent - def initialize(label: '', name:, value: nil, placeholder: '', error_message: '', helper_text: '') - super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text) + def initialize(label: '', name:, value: nil, placeholder: '', error_message: '', helper_text: '', disabled: false, id: '') + super(label: label, name: name, value: value, placeholder: placeholder, error_message: error_message, helper_text: helper_text, disabled: disabled, id: id) 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) + render Input::InputFieldComponent.new(label: @label, name: @name, value: @value, placeholder: @placeholder, + error_message: @error_message, helper_text: @helper_text, + type: @type, disabled: @disabled) end end diff --git a/app/components/layout/progress_pages_component.rb b/app/components/layout/progress_pages_component.rb new file mode 100644 index 0000000000..1680e4e494 --- /dev/null +++ b/app/components/layout/progress_pages_component.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Layout::ProgressPagesComponent < ViewComponent::Base + + renders_many :pages + def initialize(pages_title: []) + super + @pages_title = pages_title + end +end diff --git a/app/components/layout/progress_pages_component/progress_pages_component.html.haml b/app/components/layout/progress_pages_component/progress_pages_component.html.haml new file mode 100644 index 0000000000..f6b02cd67f --- /dev/null +++ b/app/components/layout/progress_pages_component/progress_pages_component.html.haml @@ -0,0 +1,32 @@ +%div.w-100{'data-controller': 'progress-pages'} + %div + .d-flex.justify-content-center.flex-column + .progress-pages-container + - @pages_title.each_with_index do |key, index| + .progress-item{'data-progress-pages-target': 'pageItem'} + %div + .outlined-circle{class: index.zero? ? 'outlined-active-circle' : ''} + = image_tag("icons/white-check.svg") + %span{class: index.zero? ? 'active' : ''} + = key.humanize + - if index < (@pages_title.size-1) + %hr.line + %div + - pages.each_with_index do |page, index| + %div.progress-content{{'data-progress-pages-target': 'pageContent'}, class: index.zero? ? '' : 'hide'} + = page + + + .progress-pages-actions + .progress-pages-back-button.hide{'data-action': "click->progress-pages#navigateBack", 'data-progress-pages-target': 'backBtn'} + = render Buttons::RegularButtonComponent.new(id: 'progress-pages-back-button', value: "Back", variant: "secondary", size: 'slim',state: 'regular') do |btn| + - btn.icon_left do + = inline_svg "icons/arrow-left.svg" + .progress-pages-next-button{'data-action': "click->progress-pages#navigateNext", 'data-progress-pages-target': 'nextBtn'} + = render Buttons::RegularButtonComponent.new(id: 'progress-pages-next-button', value: "Next", size: 'slim', state: 'regular') do |btn| + - btn.icon_right do + = inline_svg "arrow-right.svg" + .progress-pages-next-button.hide{ 'data-progress-pages-target': 'finishBtn'} + = render Buttons::RegularButtonComponent.new(id: 'progress-pages-finish-button', value: "Finish", size: 'slim', type: 'submit') do |btn| + - btn.icon_right do + = inline_svg "icons/white-check.svg" \ No newline at end of file diff --git a/app/components/layout/progress_pages_component/progress_pages_component_controller.js b/app/components/layout/progress_pages_component/progress_pages_component_controller.js new file mode 100644 index 0000000000..146922311a --- /dev/null +++ b/app/components/layout/progress_pages_component/progress_pages_component_controller.js @@ -0,0 +1,87 @@ +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ['pageItem','pageContent' ,'backBtn', 'nextBtn', 'finishBtn'] + connect() { + this.pagesItems = this.pageItemTargets + this.buttons = [this.backBtnTarget, this.nextBtnTarget, this.finishBtnTarget] + this.currentForm = 1 + } + + navigateBack(){ + this.navigateForm('back') + } + navigateNext(){ + this.navigateForm('next') + } + + navigateForm(direction) { + if (direction === "next" && this.currentForm < this.pagesItems.length) { + this.currentForm += 1 + this.showForm() + } else if (direction === "back" && this.currentForm > 1){ + this.currentForm -= 1 + this.showForm() + } + } + + showForm(){ + const targetForm = this.currentForm + for (let index = 1; index <= this.pagesItems.length; index++) { + + const targetFormDOM = this.pageContentTargets[index - 1]; + + const isCurrentForm = targetForm === index; + + targetFormDOM.classList.toggle("hide", !isCurrentForm); + + if (isCurrentForm) { + this.updateProgressBar(targetForm); + this.updateButtons(targetForm); + } + + } + + } + updateProgressBar(targetForm) { + const progressItemsDOM = document.querySelectorAll(".progress-item > div"); + const line = document.querySelectorAll(".line"); + + progressItemsDOM.forEach((item, index) => { + const isPassedSection = index + 1 < targetForm; + const isCurrentSection = index + 1 === targetForm; + + item.children[0].classList.toggle("outlined-checked-circle", isPassedSection); + item.children[0].classList.toggle("outlined-active-circle", isCurrentSection); + + + item.children[1].classList.toggle("active", isCurrentSection || isPassedSection); + + + line[index]?.classList.toggle("active", isPassedSection); + }); + } + + updateButtons(targetForm) { + + switch (targetForm) { + case 1: + this.showBtn([this.buttons[1]]); + break; + case this.pagesItems.length: + this.showBtn([this.buttons[0], this.buttons[2]]); + break; + default: + this.showBtn([this.buttons[0], this.buttons[1]]); + } + + } + + showBtn(btnIds = []) { + this.buttons.forEach((btn) => { + const targetBtnDOM = btn; + const shouldShowBtn = btnIds.includes(btn); + targetBtnDOM.classList.toggle("hide", !shouldShowBtn); + }); + } +} diff --git a/app/components/nested_form_inputs_component/nested_form_inputs_component.html.haml b/app/components/nested_form_inputs_component/nested_form_inputs_component.html.haml index 78a23641d1..730ec86f43 100644 --- a/app/components/nested_form_inputs_component/nested_form_inputs_component.html.haml +++ b/app/components/nested_form_inputs_component/nested_form_inputs_component.html.haml @@ -31,4 +31,4 @@ %div.add-another-object{data: {action:"click->nested-form#add"}} = inline_svg 'icons/plus.svg' %div - Add another #{@object_name} \ No newline at end of file + Add another #{@object_name} diff --git a/app/components/select_input_component.rb b/app/components/select_input_component.rb index 935087fe5b..99a481bb58 100644 --- a/app/components/select_input_component.rb +++ b/app/components/select_input_component.rb @@ -13,7 +13,7 @@ def initialize(id:, name:, values:, selected:, multiple: false, open_to_add_valu end def call - select_input_tag(@id, @values, @selected, multiple: @multiple, open_to_add_values: @open_to_add_values) + select_input_tag(@name, @values, @selected, multiple: @multiple, open_to_add_values: @open_to_add_values) end private @@ -32,7 +32,7 @@ def select_input_tag(id, values, selected, options = {}) 'select-input-open-add-value': open_to_add_values } } - #binding.pry + select_tag(id, options_for_select(values, selected), select_html_options) 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 0991eb213d..51ce9169ce 100644 --- a/app/components/turbo_frame_component/turbo_frame_component.html.haml +++ b/app/components/turbo_frame_component/turbo_frame_component.html.haml @@ -6,4 +6,4 @@ %div.p-3 = render LoaderComponent.new %div{'data-turbo-frame-error-target': 'errorMessage', style:'display: none; width: 100%'} - = render AlertMessageComponent.new(id: "#{@id}_error-message", type:'danger') \ No newline at end of file + = render Display::AlertComponent.new(type:'danger') \ No newline at end of file diff --git a/app/controllers/concerns/ontology_updater.rb b/app/controllers/concerns/ontology_updater.rb new file mode 100644 index 0000000000..c45020ce94 --- /dev/null +++ b/app/controllers/concerns/ontology_updater.rb @@ -0,0 +1,92 @@ +module OntologyUpdater + extend ActiveSupport::Concern + include SubmissionUpdater + def ontology_from_params + ontology = LinkedData::Client::Models::Ontology.new(values: ontology_params) + ontology.viewOf = nil unless ontology.isView + ontology + end + + def save_ontology + + @ontology = save_new_ontology + + if response_error?(@ontology) + show_new_errors(@ontology) + return + end + + + @submission = save_new_submission(params[:submission], @ontology) + + if response_error?(@submission) + @ontology.delete + show_new_errors(@submission) + else + redirect_to "/ontologies/success/#{@ontology.acronym}" + end + end + + def add_ontology_submission(acronym) + @ontology = update_existent_ontology(acronym) + + if @ontology.nil? || response_error?(@ontology) + show_new_errors(@ontology) + return + end + + @submission = @ontology.explore.latest_submission({ display: 'all' }) + submission_params = submission_params(params[:submission]) + submission_params = submission_params(ActionController::Parameters.new(@submission.to_hash.delete_if { |k, v| v.nil? || v.respond_to?(:empty?) && v.empty? })).merge(submission_params) if @submission + submission_params.delete 'submissionId' + @submission = save_new_submission(ActionController::Parameters.new(submission_params), @ontology) + + if response_error?(@submission) + show_new_errors(@submission) + else + redirect_to "/ontologies/success/#{@ontology.acronym}" + end + end + + def save_new_ontology + ontology = ontology_from_params + ontology.save + end + + def update_existent_ontology(acronym) + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + return nil if @ontology.nil? + + @ontology.update_from_params(ontology_params) + end + + def save_new_submission(submission_hash, ontology) + new_submission_params = submission_hash + new_submission_params[:ontology] = ontology.acronym + save_submission(new_submission_params) + end + + def ontology_params + p = params.require(:ontology).permit(:name, :acronym, { administeredBy: [] }, :viewingRestriction, { acl: [] }, + { hasDomain: [] }, :viewOf,:isView, :subscribe_notifications, { group: [] }) + + p[:administeredBy].reject!(&:blank?) if p[:administeredBy] + # p[:acl].reject!(&:blank?) + p[:hasDomain].reject!(&:blank?) if p[:hasDomain] + p[:group].reject!(&:blank?) if p[:group] + p.to_h + end + + def show_new_errors(object) + # TODO optimize + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true, display_links: false, display_context: false) + @categories = LinkedData::Client::Models::Category.all + @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) + @user_select_list = LinkedData::Client::Models::User.all.map { |u| [u.username, u.id] } + @user_select_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } + @errors = response_errors(object) + @ontology = ontology_from_params + @submission = submission_from_params(params[:submission]) + render 'ontologies/new' + end +end diff --git a/app/controllers/concerns/submission_updater.rb b/app/controllers/concerns/submission_updater.rb index 43746c38e2..d8c63ba724 100644 --- a/app/controllers/concerns/submission_updater.rb +++ b/app/controllers/concerns/submission_updater.rb @@ -1,11 +1,15 @@ module SubmissionUpdater extend ActiveSupport::Concern - def save_submission(new_submission_hash) + def submission_from_params(new_submission_hash) convert_values_to_types(new_submission_hash) + LinkedData::Client::Models::OntologySubmission.new(values: submission_params(new_submission_hash)) + end + def save_submission(new_submission_hash) + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(new_submission_hash[:ontology]).first - @submission = LinkedData::Client::Models::OntologySubmission.new(values: submission_params(new_submission_hash)) + @submission = submission_from_params(new_submission_hash) update_ontology_summary_only @submission.save(cache_refresh_all: false) @@ -64,12 +68,12 @@ def update_ontology_summary_only(is_remote = @submission.isRemote) def convert_values_to_types(new_submission_hash) unless new_submission_hash[:contact].nil? - new_submission_hash[:contact] = new_submission_hash[:contact].values + new_submission_hash[:contact] = new_submission_hash[:contact].values unless new_submission_hash[:contact].is_a?(Array) new_submission_hash[:contact].delete_if { |c| c[:name].empty? || c[:email].empty? } end # Convert metadata that needs to be integer to int - @metadata.map do |hash| + submission_metadata.map do |hash| if hash["enforce"].include?("integer") if !new_submission_hash[hash["attribute"]].nil? && !new_submission_hash[hash["attribute"]].eql?("") new_submission_hash[hash["attribute"].to_s.to_sym] = Integer(new_submission_hash[hash["attribute"].to_s.to_sym]) @@ -111,7 +115,7 @@ def submission_params(params) :publication ] - @metadata.each do |m| + submission_metadata.each do |m| m_attr = m["attribute"].to_sym diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index f7b9ae2fbb..a50f1797c6 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -7,6 +7,7 @@ class OntologiesController < ApplicationController include SchemesHelper, ConceptsHelper include CollectionsHelper include MappingStatistics + include OntologyUpdater require 'multi_json' require 'cgi' @@ -195,26 +196,9 @@ def properties end def create - if params[:commit].eql? 'Cancel' - redirect_to ontologies_path and return - end - @ontology = LinkedData::Client::Models::Ontology.new(values: ontology_params) - @ontology_saved = @ontology.save - if response_error?(@ontology_saved) - @categories = LinkedData::Client::Models::Category.all - @groups = LinkedData::Client::Models::Group.all(display_links: false, display_context: false) - @user_select_list = LinkedData::Client::Models::User.all.map { |u| [u.username, u.id] } - @user_select_list.sort! { |a, b| a[1].downcase <=> b[1].downcase } - @errors = response_errors(@ontology_saved) - render 'new' - else - if @ontology_saved.summaryOnly - redirect_to "/ontologies/success/#{@ontology.acronym}" - else - redirect_to new_ontology_submission_path(@ontology.acronym) - end - end + # redirect_to ontologies_path and return if params[:commit].eql? 'Cancel' + save_ontology end def edit @@ -239,7 +223,8 @@ def mappings def new @ontology = LinkedData::Client::Models::Ontology.new - @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true,display_links: false, display_context: false) + @submission = LinkedData::Client::Models::OntologySubmission.new + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym', include_views: true, display_links: false, display_context: false) @categories = LinkedData::Client::Models::Category.all @groups = LinkedData::Client::Models::Group.all @user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]} @@ -454,9 +439,9 @@ def widgets render partial: 'ontologies/sections/widgets', layout: 'ontology_viewer' end end - + def show_licenses - + @metadata = submission_metadata @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first @licenses= ["hasLicense","morePermissions","copyrightHolder"] @@ -464,28 +449,14 @@ def show_licenses render partial: 'ontologies/sections/licenses' end def ajax_ontologies - - + + render json: LinkedData::Client::Models::Ontology.all(include_views: true, display: 'acronym,name', display_links: false, display_context: false) end - private - - - - def ontology_params - p = params.require(:ontology).permit(:name, :acronym, { administeredBy:[] }, :viewingRestriction, { acl:[] }, - { hasDomain:[] }, :isView, :viewOf, :subscribe_notifications, {group:[]}) - - p[:administeredBy].reject!(&:blank?) - p[:acl].reject!(&:blank?) - p[:hasDomain].reject!(&:blank?) - p[:group].reject!(&:blank?) - p.to_h - end - + private def get_views(ontology) views = ontology.explore.views || [] views.select!{ |view| view.access?(session[:user]) } diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 03f91c9d6f..7680a62d22 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,5 +1,5 @@ class SubmissionsController < ApplicationController - include SubmissionsHelper, SubmissionUpdater + include SubmissionsHelper, SubmissionUpdater, OntologyUpdater layout :determine_layout before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new] before_action :submission_metadata, only: [:create, :edit, :new, :update, :index] @@ -28,28 +28,17 @@ def new @submission = @ontology.explore.latest_submission @submission ||= LinkedData::Client::Models::OntologySubmission.new @submission.id = nil + @categories = LinkedData::Client::Models::Category.all + @groups = LinkedData::Client::Models::Group.all + @user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]} + @user_select_list.sort! {|a,b| a[1].downcase <=> b[1].downcase} + @is_update_ontology = true end # Called when form to "Add submission" is submitted def create - # Make the contacts an array - _, submission_params = params[:submission].each.first - @required_only = !params['required-only'].nil? - @filters_disabled = true - @submission_saved = save_submission(submission_params) - if response_error?(@submission_saved) - @errors = response_errors(@submission_saved) # see application_controller::response_errors - if @errors && @errors[:uploadFilePath] - @errors = ["Please specify the location of your ontology"] - elsif @errors && @errors[:contact] - @errors = ["Please enter a contact"] - end - - reset_agent_attributes - render 'new', status: 422 - else - redirect_to "/ontologies/success/#{@ontology.acronym}" - end + @is_update_ontology = true + add_ontology_submission(params[:ontology][:acronym] || params[:id]) end # Called when form to "Edit submission" is submitted diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 323f301f11..396f688839 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -34,7 +34,7 @@ def omniauth_providers_info end def omniauth_provider_info(strategy) - omniauth_providers_info.select {|k,v| v[:strategy].eql?(strategy.to_sym)} + omniauth_providers_info.select {|k,v| v[:strategy].eql?(strategy.to_sym) || k.eql?(strategy)} end def omniauth_token_provider(strategy) @@ -367,14 +367,14 @@ def get_groups_data def metadata_for_select get_metadata return @metadata_for_select - end + end def get_metadata @metadata_for_select = [] submission_metadata.each do |data| @metadata_for_select << data["attribute"] end - end + end def ontologies_to_acronyms(ontologyIDs) @@ -426,7 +426,7 @@ def add_proposal_button(parent_id, parent_type) class: "add_proposal btn btn-primary", data: { show_modal_title_value: "Add a new proposal"} end end - + def subscribe_button(ontology_id) ontology_acronym = ontology_id.split('/').last @@ -611,10 +611,10 @@ def skos? submission = @submission || @submission_latest submission&.hasOntologyLanguage === 'SKOS' end - + def current_page?(path) request.path.eql?(path) - end + end def request_lang lang = params[:language] || params[:lang] @@ -646,12 +646,9 @@ def bp_config_json config[:ncbo_slice] = @subdomain_filter[:acronym] if (@subdomain_filter[:active] && !@subdomain_filter[:acronym].empty?) config.to_json end - - def portal_name $SITE - end - + end def navitems items = [["/ontologies", "Browse"],["/mappings", "Mappings"],["/recommender", "Recommender"],["/annotator", "Annotator"], ["/landscape", "Landscape"]] end diff --git a/app/helpers/collections_helper.rb b/app/helpers/collections_helper.rb index 9f6de40089..40783b6666 100644 --- a/app/helpers/collections_helper.rb +++ b/app/helpers/collections_helper.rb @@ -42,7 +42,7 @@ def no_collections? end def no_collections_alert - render AlertMessageComponent.new(id: 'collection-empty-info') do + render Display::AlertComponent.new do "#{@ontology.acronym} does not contain collections (skos:Collection)" end end diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb new file mode 100644 index 0000000000..ff0f14294a --- /dev/null +++ b/app/helpers/inputs_helper.rb @@ -0,0 +1,45 @@ +module InputsHelper + + def text_input(label: nil, name: , value:, disabled: false) + render Input::TextInputComponent.new(label: input_label(label, name) , name: name, value: value, error_message: input_error_message(name), disabled: disabled) + end + + def select_input(label: nil, name: , values:, selected: nil, multiple: false) + render Input::SelectComponent.new(label: input_label(label, name), name: name, value: values, selected: selected, multiple: multiple) + end + + def check_input(id:, name: , label: '', value:, checked: false) + render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked) + end + + def switch_input(id: , name:, label: ,checked: false) + render SwitchInputComponent.new(id: id, name: name, label: label, checked: checked) + end + + def url_input(label: nil, name: , value:) + render Input::UrlComponent.new(label: input_label(label, name), name: name, value: value, error_message: input_error_message(name) ) + end + + def text_area_input(label: nil, name: , value:) + render Input::TextAreaComponent.new(label: input_label(label, name), name: name, value: value, error_message: input_error_message(name)) + end + + def date_input(label: nil, name:, value:) + render Input::DateComponent.new(label: input_label(label, name) ,name: name, value: value || Date.today, error_message: input_error_message(name)) + end + + private + + def method_name(name) + match = /.*\[(.*?)\]/.match(name) + match.nil? ? name : match[1] + end + + def input_label(label, name) + label || method_name(name).humanize + end + + def input_error_message(name) + attribute_error(method_name(name)) + end +end \ No newline at end of file diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index b421e5ae41..cf35e422d5 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -534,10 +534,26 @@ def ontology_edit_button def submission_json_button render RoundedButtonComponent.new(link: "#{(@submission_latest || @ontology).id}?display=all", target: '_blank', size: 'medium') end + + def attribute_error(attr) + return '' unless @errors && @errors[attr.to_sym] + errors = @errors[attr.to_sym] + + errors.values.join(', ') + end + + def error_message + if !@errors[:error].nil? && @errors[:error].is_a?(String) + @errors[:error] + else + "Errors in fields #{@errors.keys.join(', ')}" + end + + end private def submission_languages(submission = @submission) - submission.naturalLanguage.map { |natural_language| natural_language["iso639"] && natural_language.split('/').last }.compact + submission&.naturalLanguage.map { |natural_language| natural_language["iso639"] && natural_language.split('/').last }.compact end end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 01b3993da5..55d59c4e1a 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -63,12 +63,12 @@ def no_schemes? end def no_main_scheme_alert - render AlertMessageComponent.new(id: 'main-scheme-empty-info') do + render Display::AlertComponent.new do 'no main scheme defined in the URI attribute' end end def no_schemes_alert - render AlertMessageComponent.new(id: 'schemes-empty-info') do + render Display::AlertComponent.new do "#{@ontology.acronym} does not contain schemes (skos:ConceptScheme)" end end diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index cc338b9942..ca55ce95b5 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -338,12 +338,7 @@ def generate_agent_input(attr, type: 'person') def generate_date_input(attr) field_id = [:submission, attr["attribute"].to_s, @ontology.acronym].join('_') date_value = @submission.send(attr["attribute"]).presence - data_flat_picker = { controller: "flatpickr", flatpickr_date_format: "Y-m-d", flatpickr_alt_input: "true", flatpickr_alt_format: "F j, Y" } - content_tag(:div, class: 'input-group') do - [ - date_field(object_name, attr["attribute"].to_s.to_sym, value: date_value, id: field_id, data: data_flat_picker, class: "not-disabled") - ].join.html_safe - end + render Input::DateComponent.new(label: (attr["label"] || attr["attribute"]).to_s ,name: object_name, value: date_value || Date.today, id: field_id) end def generate_textarea_input(attr) @@ -355,10 +350,13 @@ def generate_select_input(attr, name, select_values, metadata_values, multiple: render SelectInputComponent.new(id: id, name: name, values: select_values , selected: metadata_values , multiple: multiple) end - def generate_list_field_input(attr, name, values, field_func) + def generate_list_field_input(attr, name, label, values, &block) render NestedFormInputsComponent.new do |c| - c.template do - method(field_func).call("#{name}[NEW_RECORD]", '', :id => attr["attribute"].to_s + "_" + @ontology.acronym, class: "metadataInput form-control my-1") + c.header do + content_tag(:div, label) + end + c.template do + block.call('', "#{name}[NEW_RECORD]", attr["attribute"].to_s + "_" + @ontology.acronym) end c.empty_state do @@ -367,20 +365,25 @@ def generate_list_field_input(attr, name, values, field_func) values.each_with_index do |metadata_val, i| c.row do - method(field_func).call("#{name}[#{i}]", metadata_val, :id => "submission_#{attr["attribute"].to_s}" + "_" + @ontology.acronym, class: "metadataInput my-1 form-control") + block.call(metadata_val, "#{name}[#{i}]" ,"submission_#{attr["attribute"].to_s}" + "_" + @ontology.acronym) end end end end - def generate_url_input(attr, name, values) - generate_list_field_input(attr, name, values, :url_field_tag) + def generate_url_input(attr, name, values, label:"") + generate_list_field_input(attr, name, label, values) do |value, row_name, id| + render Input::UrlComponent.new(label: "", name: row_name, value: value, id: id) + end end - def generate_list_text_input(attr, name, values) - generate_list_field_input(attr, name, values, :text_field_tag) + def generate_list_text_input(attr, name, values, label:"") + generate_list_field_input(attr, name, label, values) do |value, row_name, id| + render Input::TextInputComponent.new(label: "", name: row_name, value: value, id: id) + end end + def generate_boolean_input(attr, name) value = attribute_values(attr) value = value.to_s unless value.nil? @@ -412,7 +415,7 @@ def generate_attribute_input(attr_label, options = {}) input_html = ''.html_safe # Get the attribute hash corresponding to the given attribute - attr = @metadata.select { |attr_hash| attr_hash["attribute"].to_s.eql?(attr_label) }.first + attr = submission_metadata.select { |attr_hash| attr_hash["attribute"].to_s.eql?(attr_label) }.first object_name, name = attribute_input_name(attr["attribute"]) diff --git a/app/helpers/turbo_helper.rb b/app/helpers/turbo_helper.rb index 3e1f71155f..c1b0303b46 100644 --- a/app/helpers/turbo_helper.rb +++ b/app/helpers/turbo_helper.rb @@ -9,7 +9,7 @@ def alerts_container_id(id = nil) def alert(id: nil, type: 'success', &block) turbo_stream.prepend(id ||alerts_container_id) do - AlertMessageComponent.new(type: type).render_in(view_context, &block) + Display::AlertComponent.new(type: type).render_in(view_context, &block) end end diff --git a/app/javascript/component_controllers/index.js b/app/javascript/component_controllers/index.js index 5cb73bbeb5..0a94e7a7eb 100644 --- a/app/javascript/component_controllers/index.js +++ b/app/javascript/component_controllers/index.js @@ -16,6 +16,10 @@ import CircleProgressBarComponentController from "../../components/circle_progre import Tabs_container_component_controller from "../../components/tabs_container_component/tabs_container_component_controller"; +import alert_component_controller from "../../components/display/alert_component/alert_component_controller"; +import Progress_pages_component_controller + from "../../components/layout/progress_pages_component/progress_pages_component_controller"; + application.register("turbo-modal", TurboModalController) application.register("file-input", FileInputLoaderController) @@ -25,3 +29,5 @@ application.register("subscribe-notes", Ontology_subscribe_button_component_cont application.register("search-input", Search_input_component_controller) application.register("tabs-container", Tabs_container_component_controller) application.register("circle-progress-bar", CircleProgressBarComponentController) +application.register("alert-component", alert_component_controller) +application.register("progress-pages", Progress_pages_component_controller) diff --git a/app/javascript/controllers/tom_select_controller.js b/app/javascript/controllers/tom_select_controller.js new file mode 100644 index 0000000000..4f157b2aaa --- /dev/null +++ b/app/javascript/controllers/tom_select_controller.js @@ -0,0 +1,16 @@ +import { Controller } from "@hotwired/stimulus" +import TomSelect from "tom-select" +// Connects to data-controller="tom-select" +export default class extends Controller { + + + + connect() { + + new TomSelect(this.element, { + + + //plugins: ['remove_button'] + }); + } +} diff --git a/app/views/concepts/_details.html.haml b/app/views/concepts/_details.html.haml index a83f051fa7..a98a909b0d 100644 --- a/app/views/concepts/_details.html.haml +++ b/app/views/concepts/_details.html.haml @@ -3,7 +3,7 @@ - label_xl_set = %w[skos-xl#prefLabel skos-xl#altLabel skos-xl#hiddenLabel] - if @concept.prefLabel.nil? %div.py-3.px-2 - = render(AlertMessageComponent.new(message: t('ontology_details.concept.no_preferred_name_for_selected_language'), type: "warning", closeable: true)) + = render Display::AlertComponent.new(message: t('ontology_details.concept.no_preferred_name_for_selected_language'), type: "warning", closable: true) = render ConceptDetailsComponent.new(id:'concept-details', acronym: @ontology.acronym, properties: @concept.properties, diff --git a/app/views/layouts/_topnav.html.haml b/app/views/layouts/_topnav.html.haml index 2da968c7ec..6c8deed355 100644 --- a/app/views/layouts/_topnav.html.haml +++ b/app/views/layouts/_topnav.html.haml @@ -44,7 +44,7 @@ = link_to("Logout", logout_path) = select_tag('language', options_for_select([['EN','en'],['FR','fr']]), id: 'language-select', class: 'nav-language', - data: { controller: "platform-language", action: "change->platform-language#handleLangChanged" }) + data: { controller: "platform-language", action: "change->platform-language#handleLangChanged" }) = render DropdownButtonComponent.new do |d| - d.header do diff --git a/app/views/layouts/component_preview.html.erb b/app/views/layouts/component_preview.html.erb index 5369cd0531..429f630109 100644 --- a/app/views/layouts/component_preview.html.erb +++ b/app/views/layouts/component_preview.html.erb @@ -55,5 +55,6 @@ <%= javascript_include_tag "application" %> + \ No newline at end of file diff --git a/app/views/login/index.html.haml b/app/views/login/index.html.haml index 6914a979c7..41ce080fed 100644 --- a/app/views/login/index.html.haml +++ b/app/views/login/index.html.haml @@ -15,7 +15,7 @@ %a.login-forgot-password{:href => "/lost_pass"} %p Forgot password? .login-button-container - = render Buttons::RegularButtonComponent.new(id:"login", value: "Login", type: "submit") + = render Buttons::RegularButtonComponent.new(id: 'login-button', value: "Login", type:'submit') %p.dont-have-account Don't have an account? %a.text-decoration-none{:href => new_user_path} Register diff --git a/app/views/mappings/bulk_loader/_loaded_mappings.html.haml b/app/views/mappings/bulk_loader/_loaded_mappings.html.haml index 522cbd690b..0798d7ee02 100644 --- a/app/views/mappings/bulk_loader/_loaded_mappings.html.haml +++ b/app/views/mappings/bulk_loader/_loaded_mappings.html.haml @@ -1,14 +1,14 @@ %div.w-100.h-100 - if errors - errors = errors.map {|k,v| "Mapping #{k.to_s.to_i + 1} errors: #{v}"} - = render AlertMessageComponent.new(type:'danger') do + = render Display::AlertComponent.new(type:'danger') do - errors.each do |error| %p = error %div.overflow-scroll - if created = render AlertsContainerComponent.new(id: alerts_container_id) do - = render AlertMessageComponent.new(type: 'success') do + = render Display::AlertComponent.new(type: 'success') do #{created.size} mappings created successfully %table.table.table-sm.zebra{data: {controller: 'data-table'}, style:"table-layout: fixed; font-size: 12px; word-break: break-word;"} %thead diff --git a/app/views/ontologies/_form.html.haml b/app/views/ontologies/_form.html.haml index fe945e9f65..012a3056ab 100644 --- a/app/views/ontologies/_form.html.haml +++ b/app/views/ontologies/_form.html.haml @@ -1,220 +1,130 @@ -- button_text ||= "Create ontology" -- title_text ||= "Submit New Ontology" +.upload-ontology-container + %div{style: 'width: 589px'} + - unless @errors.nil? + = render Display::AlertComponent.new(message: error_message, type: 'danger', closable: false) + .upload-ontology-card + .upload-ontology-center + .Upload-ontology-title + %div + = @is_update_ontology ? "Upload new update" : "Upload ontology" + %hr + .upload-ontology-progress + = render Layout::ProgressPagesComponent.new(pages_title: ['Details', 'General metadata', 'Dates contacts']) do |c| + - c.page do + .upload-ontology-input-field-container + = text_input(name: 'ontology[name]', value: @ontology.name) + .upload-ontology-input-field-container + = text_input(name: 'ontology[acronym]', value: @ontology.acronym, disabled: @is_update_ontology) + = hidden_field_tag 'ontology[acronym]', @ontology.acronym if @is_update_ontology + + .upload-ontology-input-field-container#visibilityContainer + = select_input(label: "Visibility", name: "ontology[viewingRestriction]", values: ["public","private"], selected: @ontology.viewingRestriction ) + .upload-ontology-input-field-container#visibility-group{style: 'display: none'} + = select_input(label: "Add or remove accounts that are allowed to view classes in this ontology using the account name", name: "ontology[acl]", values: @user_select_list, selected: @ontology.acl, multiple: true) + + .upload-ontology-input-field-container + = select_input(label: "Administrator", name: "ontology[administeredBy]", values: @user_select_list, selected: @ontology.administeredBy || session[:user].id, multiple: true) + .upload-ontology-input-field-container + = render Input::InputFieldComponent.new(name: '', label:'Categories') do + %div.upload-ontology-chips-container + - @categories.each do |category| + = check_input(name: "ontology[hasDomain][]", id: category[:acronym] , label: category[:acronym], value: category[:id], checked: @ontology.hasDomain&.any?{|x| x.eql?(category[:id])}) + .upload-ontology-field-container + = render Input::InputFieldComponent.new(name: '', label:'Groups') do + %div.upload-ontology-chips-container + - @groups.each do |group| + = check_input(name: "ontology[group][]", id: group[:acronym] , label: group[:acronym], value: group[:id], checked: @ontology.group&.any?{|x| x.eql?(group[:id])}) + + .upload-ontology-input-field-container.mt-2 + %span.d-flex + = switch_input(id: 'ontology_isView', name: 'ontology[isView]', label: 'Is a view of another ontology?', checked: @ontology.view?) + %div#ontology_viewOf{style: "display: #{ !@ontology.view? ? 'none' : 'block'}"} + = render partial: "shared/ontology_picker_single", locals: {placeholder: "", field_name: "viewOf", selected: @ontology.viewOf} + + - c.page do + .upload-ontology-desc + %div + To understand the ontologies metadata: + %a{:href => "#seethewiki"} + see the Wiki + %svg{:fill => "none", :height => "8", :viewbox => "0 0 8 8", :width => "8", :xmlns => "http://www.w3.org/2000/svg"} + %path{:d => "M5.77776 8H1.33333C0.977156 8 0.642334 7.8613 0.390512 7.60946C0.138689 7.35762 0 7.02278 0 6.66666V2.22222C0 1.86607 0.138704 1.53124 0.390527 1.27942C0.64235 1.0276 0.977172 0.888894 1.33334 0.888894H3.11111C3.35659 0.888894 3.55556 1.08787 3.55556 1.33334C3.55556 1.57881 3.35659 1.77779 3.11111 1.77779H1.33333C1.2146 1.77779 1.10301 1.82402 1.01907 1.90795C0.935144 1.99188 0.888894 2.1035 0.888894 2.22222V6.66666C0.888894 6.78538 0.935129 6.89698 1.01907 6.98094C1.10301 7.06486 1.2146 7.11111 1.33333 7.11111H5.77775C5.89647 7.11111 6.00807 7.06487 6.09202 6.98091C6.17595 6.89698 6.22218 6.78537 6.22218 6.66664V4.88889C6.22218 4.64341 6.42117 4.44445 6.66664 4.44445C6.91212 4.44445 7.11111 4.64343 7.11111 4.88889V6.66666C7.11111 7.02281 6.9724 7.35762 6.72056 7.60947C6.46872 7.8613 6.13389 8 5.77776 8ZM3.11111 5.33332C2.99736 5.33332 2.88362 5.28994 2.79685 5.20315C2.62329 5.02959 2.62329 4.74816 2.79685 4.5746L6.48254 0.888894H4.88889C4.64341 0.888894 4.44445 0.68992 4.44445 0.444447C4.44445 0.198974 4.64341 0 4.88889 0H7.55555C7.61702 0 7.67556 0.0124825 7.72882 0.0350409C7.77851 0.0560624 7.82518 0.0865233 7.86602 0.126439L7.86605 0.12647C7.86634 0.126765 7.86664 0.127045 7.86692 0.12734C7.86699 0.127417 7.8671 0.127511 7.86718 0.127588C7.8674 0.127805 7.86765 0.128038 7.86786 0.128271C7.86802 0.128427 7.86816 0.128566 7.86831 0.128721C7.86848 0.128892 7.86867 0.129079 7.86881 0.129218C7.86912 0.129529 7.86946 0.129855 7.86977 0.130181C7.87008 0.130491 7.87042 0.130833 7.87074 0.131143C7.87091 0.131299 7.87109 0.131501 7.87122 0.13164C7.87139 0.131796 7.87151 0.131935 7.87167 0.132091C7.87191 0.132323 7.87213 0.132541 7.87235 0.132789C7.87243 0.132851 7.87254 0.13296 7.8726 0.133038C7.87289 0.133333 7.87319 0.133628 7.87347 0.133923L7.8735 0.133954C7.9134 0.174817 7.94388 0.221486 7.96488 0.271167C7.98744 0.32442 7.99994 0.382951 7.99994 0.444431V3.1111C7.99994 3.35657 7.80095 3.55555 7.55548 3.55555C7.31 3.55555 7.11104 3.35657 7.11104 3.1111V1.51744L3.4253 5.20317C3.33859 5.28995 3.22485 5.33332 3.11111 5.33332Z", :fill => "#31B404"} + .upload-ontology-input-field-container + = url_input(label: 'URL', name: "submission[URI]", value: @submission.URI) + .upload-ontology-input-field-container + = text_area_input(name: "submission[description]", value: @submission.description) + + - if @is_update_ontology + .upload-ontology-input-field-container + = generate_list_text_input("notes", "submission[notes]", Array(@submission.notes), label: "Change notes") + .upload-ontology-field-container + = select_input(label: "Format", name: "submission[hasOntologyLanguage]", values: ["OBO", "OWL", "SKOS", "UMLS"], selected: @submission.hasOntologyLanguage) + .upload-ontology-desc.hide + %div + SKOS vocabularies submitted to BioPortal must contain a minimum of one concept scheme and top concept assertion. Please + refer to the NCBO wiki for a more + %a{:href => "#seethewiki"} + detailed explanation + %svg{:fill => "none", :height => "8", :viewbox => "0 0 8 8", :width => "8", :xmlns => "http://www.w3.org/2000/svg"} + %path{:d => "M5.77776 8H1.33333C0.977156 8 0.642334 7.8613 0.390512 7.60946C0.138689 7.35762 0 7.02278 0 6.66666V2.22222C0 1.86607 0.138704 1.53124 0.390527 1.27942C0.64235 1.0276 0.977172 0.888894 1.33334 0.888894H3.11111C3.35659 0.888894 3.55556 1.08787 3.55556 1.33334C3.55556 1.57881 3.35659 1.77779 3.11111 1.77779H1.33333C1.2146 1.77779 1.10301 1.82402 1.01907 1.90795C0.935144 1.99188 0.888894 2.1035 0.888894 2.22222V6.66666C0.888894 6.78538 0.935129 6.89698 1.01907 6.98094C1.10301 7.06486 1.2146 7.11111 1.33333 7.11111H5.77775C5.89647 7.11111 6.00807 7.06487 6.09202 6.98091C6.17595 6.89698 6.22218 6.78537 6.22218 6.66664V4.88889C6.22218 4.64341 6.42117 4.44445 6.66664 4.44445C6.91212 4.44445 7.11111 4.64343 7.11111 4.88889V6.66666C7.11111 7.02281 6.9724 7.35762 6.72056 7.60947C6.46872 7.8613 6.13389 8 5.77776 8ZM3.11111 5.33332C2.99736 5.33332 2.88362 5.28994 2.79685 5.20315C2.62329 5.02959 2.62329 4.74816 2.79685 4.5746L6.48254 0.888894H4.88889C4.64341 0.888894 4.44445 0.68992 4.44445 0.444447C4.44445 0.198974 4.64341 0 4.88889 0H7.55555C7.61702 0 7.67556 0.0124825 7.72882 0.0350409C7.77851 0.0560624 7.82518 0.0865233 7.86602 0.126439L7.86605 0.12647C7.86634 0.126765 7.86664 0.127045 7.86692 0.12734C7.86699 0.127417 7.8671 0.127511 7.86718 0.127588C7.8674 0.127805 7.86765 0.128038 7.86786 0.128271C7.86802 0.128427 7.86816 0.128566 7.86831 0.128721C7.86848 0.128892 7.86867 0.129079 7.86881 0.129218C7.86912 0.129529 7.86946 0.129855 7.86977 0.130181C7.87008 0.130491 7.87042 0.130833 7.87074 0.131143C7.87091 0.131299 7.87109 0.131501 7.87122 0.13164C7.87139 0.131796 7.87151 0.131935 7.87167 0.132091C7.87191 0.132323 7.87213 0.132541 7.87235 0.132789C7.87243 0.132851 7.87254 0.13296 7.8726 0.133038C7.87289 0.133333 7.87319 0.133628 7.87347 0.133923L7.8735 0.133954C7.9134 0.174817 7.94388 0.221486 7.96488 0.271167C7.98744 0.32442 7.99994 0.382951 7.99994 0.444431V3.1111C7.99994 3.35657 7.80095 3.55555 7.55548 3.55555C7.31 3.55555 7.11104 3.35657 7.11104 3.1111V1.51744L3.4253 5.20317C3.33859 5.28995 3.22485 5.33332 3.11111 5.33332Z", :fill => "#31B404"} + with examples. + .upload-ontology-field-container.mt-3 + = select_input(name: "submission[status]", values: ["alpha", "beta", "production", "retired"], selected: @submission.status) + .upload-ontology-field-container + .mt-3.mb-2 Location + = render partial: 'ontologies/submission_location_form' + + - c.page do + .upload-ontology-input-field-container + - if @is_update_ontology + = date_input(label: 'Modification date (dd/mm/yy)', name: 'submission[modificationDate]', value: @submission.modificationDate) + - else + = date_input(label: 'Date of original creation (dd/mm/yy)', name: 'submission[released]', value: @submission.released) + .upload-ontology-contact + = render Input::InputFieldComponent.new(name:'', error_message: attribute_error(:contact)) do + = render NestedFormInputsComponent.new(object_name: "Contact") do |c| + - c.header do + - content_tag(:div, 'Contact name', class: 'w-50') + content_tag(:div, 'Contact email', class: 'w-50') + - c.template do + = content_tag(:div, class: "d-flex my-1") do + .w-50.mr-2 + = render Input::TextInputComponent.new(label: "", name: "submission[contact][NEW_RECORD][name]") + .w-50 + = render Input::TextInputComponent.new(label: "", name: "submission[contact][NEW_RECORD][email]") + - Array(@submission.contact).each_with_index do |contact, i| + - c.row do + = content_tag(:div, class: "d-flex my-1") do + .w-50.mr-2 + = render Input::TextInputComponent.new(label: "", name: "submission[contact][#{i}][name]", value: contact["name"]) + .w-50 + = render Input::TextInputComponent.new(label: "", name: "submission[contact][#{i}][email]", value: contact["email"]) :javascript - function hideAllRestrictions() { - jQuery(".viewing_restriction_disabled").attr("disabled", true); - jQuery("div.viewing_restriction_types").addClass("hidden"); - } - - function showRestrictionPrivate() { - jQuery("#ontology_acl").removeAttr("disabled"); - jQuery("#viewingRestrictionsPrivate").removeClass("hidden"); - } - - function showRestrictionLicensed() { - jQuery("#ontology_licenseInformation").removeAttr("disabled"); - jQuery("#viewingRestrictionsLicensed").removeClass("hidden"); - } - jQuery(document).data().bp.acronyms = #{raw LinkedData::Client::Models::Ontology.all.map {|o| o.acronym}.to_json}; + function showPrivateAclSelect() { + const visibilityGroupDiv = document.getElementById('visibility-group'); + // Get the selected value from the select element + const selectElement = document.getElementById('select_ontology[viewingRestriction]'); + const selectedValue = selectElement.value; - jQuery(document).ready(function(){ - // Wire up options for restriction how an ontology is viewed - jQuery("#ontology_viewingRestriction").change(function(){ - var select = jQuery(this); - if (select.val() == "private") { - hideAllRestrictions() - showRestrictionPrivate(); - } else if (select.val() == "licensed") { - hideAllRestrictions(); - showRestrictionLicensed(); - } else if (select.val() == "public") { - hideAllRestrictions(); - } - }); - // Make sure you can see the account select if the select list has private selected - if (jQuery("#ontology_viewingRestriction").val() == "private") { - showRestrictionPrivate(); - } else if (jQuery("#ontology_viewingRestriction").val() == "licensed") { - showRestrictionLicensed(); + // Check if the selected value is "private" + if (selectedValue === 'private') { + // If it's "private", show the visibility-group div + visibilityGroupDiv.style.display = 'block'; + } else { + // If it's not "private", hide the visibility-group div + visibilityGroupDiv.style.display = 'none'; } + } + const parentDiv = document.getElementById('visibilityContainer'); + parentDiv.addEventListener('change', () => { showPrivateAclSelect() }); + - jQuery("#ontology_isView").live("click", function(){ - console.log(jQuery("#ontology_isView").is(":checked")) + jQuery("#ontology_isView").live("click", function(){ if (jQuery("#ontology_isView").is(":checked")) { - jQuery("#ontology_viewOf").removeAttr('disabled').trigger("liszt:updated"); + jQuery("#ontology_viewOf").removeAttr('disabled').show(); } else { - jQuery("#ontology_viewOf").attr('disabled', true).trigger("liszt:updated"); + jQuery("#ontology_viewOf").attr('disabled', true).hide(); } - }); - - // Wire up chosen selectors - jQuery("#ontology_administeredBy").chosen(); - jQuery("#ontology_acl").chosen(); - jQuery("#ontology_hasDomain").chosen(); - jQuery("#ontology_group").chosen(); - - // Make acronym upcase as you type - jQuery("#ontology_acronym").on('input', function(e) { - var input = $(this); - var start = input[0].selectionStart; - $(this).val(function (_, val) { - return val.toUpperCase(); - }); - input[0].selectionStart = input[0].selectionEnd = start; - }); - - // Check acronym as you type - jQuery("#ontology_acronym").on('input', function(e) { - var $this = $(this); - var errors = []; - var errorHTML = ""; - - if ($this.val().match("^[^a-z^A-Z]{1}")) { - errors.push("Acronym must start with a letter"); - } - - if ($this.val().match("[^-_0-9a-zA-Z]")) { - errors.push("Acronym must only contain the folowing characters: -, _, letters, and numbers"); - } - - if ($this.val().match(".{17,}")) { - errors.push("Acronym must be sixteen characters or less"); - } - - if (jQuery(document).data().bp.acronyms.indexOf($this.val()) !== -1) { - errors.push("Acronym already in use"); - } - - if (errors.length > 0) { - errorHTML = "
  • " + errors.join("
  • ") + "
  • "; - } - - jQuery("#acronym_errors").html(errorHTML); - }); - - jQuery("#ontologyForm").validate({ - errorClass: "ontologyFormError", - errorElement: "div", - rules: { - "ontology[name]": "required", - "ontology[acronym]": "required", - }, - messages: { - "ontology[name]": "Please enter a name", - "ontology[acronym]": "Please enter an acronym", - }, - }); - }); - -:css - div.ontologyFormError { - color: red; - padding-top: 3px; - } - -- unless @errors.nil? - .enable-lists{:style => "color:red;"} - %strong Errors On Form - %ul - - if @errors[:error].instance_of? OpenStruct - - errors = @errors[:error].to_h - - errors.delete :links - - errors.delete :context - - errors.to_h.each do |errors_field, error| - - next if error.nil? - - %li - - if error.instance_of? OpenStruct - - error_hash = error.to_h - - error_hash.delete :links - - error_hash.delete :context - - error_hash.each do |error_type, e| - = "#{error_type} : #{e}" - - else - = errors_field - - else - -# A generic fallback - = @errors.to_json - -%div.p-5 - %div - %h1 - #{title_text} - - %div.p-5.card - %small.asterik.mb-2.text-right - * fields are required - %div.form-row - %div.form-group.col-md-6 - = f.label :name, "Name" - %span.asterik * - = f.text_field :name, value: @ontology.name, class:"form-control" - %div.form-group.col-md-3 - = f.label :acronym, "Acronym" - %span.asterik * - - acronym_enabled = @ontology.acronym.nil? || ! @errors.nil? - = f.text_field(:acronym, value: @ontology.acronym, :disabled => ! acronym_enabled, data: { acronyms: acronyms(@ontologies) }, class:"form-control") - %ul#acronym_errors.enable-lists{style: "color: red; padding: 3px;"} - %div.form-group.col-md-3 - %div - - viewing_help = "Public ontologies; will be accessible to everyone via UI and API. Download can be desactivated on demand.
    Private ontologies; are only accessible via UI and API to logged users listed explicitly." - = f.label :viewingRestriction do - = render partial: "shared/ui-component/label_with_help", locals:{help_text: viewing_help, id:"viewing_tooltip" ,label: "Viewing Restriction"} - - view_restiction_options = [["Public", "public"], ["Private", "private"]] - - selected = @ontology.private? ? "private" : "" - - selected = @ontology.licensed? ? "licensed" : selected - - display_private = @ontology.private? ? "" : "hidden" - - display_licensed = @ontology.licensed? ? "" : "hidden" - = f.select :viewingRestriction, view_restiction_options, { :selected => selected }, class:"form-control" - - %div.form-row - #viewingRestrictionsPrivate.form-group.col.viewing_restriction_types{class: display_private} - = f.label :acl do - Add or remove accounts that are allowed to view classes in this ontology using the account name - = f.select(:acl, @user_select_list, {include_blank: true, selected: @ontology.acl}, {multiple: true, :"data-placeholder" => "Select users who have access", class:"form-control"}) - %div.form-row - %div#viewingRestrictionsLicensed.form-group.col.viewing_restriction_types{class: display_licensed} - = f.label :licenseInformation do - %b License Text: - The text below explains what licensing information you want to collect before allowing access. We will display this text and record the user's response when the user attempts to access your ontology. - - disabled = @ontology.licensed? ? {} : {:disabled => "true"} - = f.text_area :licenseInformation, { :rows => 5, :class => "viewing_restriction_disabled form-control", :style => "width: 90%;" }.merge(disabled) - - %div.form-row.form-group - %div.col-md-1 - = f.label :administeredBy, "Administrators", class:" col-form-label" - %span.asterik * - %div.col-md-11 - = f.select(:administeredBy, @user_select_list ,{selected: @ontology.administeredBy || session[:user].id}, {multiple: true, :"data-placeholder" => "Select administrators", class:"form-control"}) - %div.form-group.row - %div.col-md-1 - = f.label :hasDomain, "Categories", class:"col-form-label" - %div.col-md-11 - - cat_select = @categories.sort{|a,b| a.name <=> b.name}.map{|c| [c.name, c.id]} - = f.hidden_field(:hasDomain, {value: "", id: "ontology_hasDomain_empty_select_hack", name: "ontology[hasDomain][]"}) - = f.select(:hasDomain, cat_select, {selected: @ontology.hasDomain}, {multiple: true, :"data-placeholder" => "Select category (domain)", class:"form-control"}) - %div.form-group.row - %div.col-md-1 - = f.label :group, "Groups", class:"col-form-label" - %div.col-md-11 - - group_select = @groups.sort{|a,b| a.name <=> b.name}.map{|c| [c.name, c.id]} - = f.hidden_field(:group, {value: "", id: "ontology_group_empty_select_hack", name: "ontology[group][]"}) - = f.select(:group, group_select, {selected: @ontology.group}, {multiple: true, :"data-placeholder" => "Select group", class:"form-control"}) - %div.form-row - %div.from-group.col-md-6 - = f.label :isView, "This ontology is a view of:" - = f.check_box :isView, checked: @ontology.view? - %div#viewOf_picker.row.form-group - - single_picker_locals = {:picker_id => "ontology_viewOf", placeholder: "Select an ontology to create a view on", field_name: "viewOf", disabled: !@ontology.view?, selected: @ontology.viewOf} - = render :partial => "shared/ontology_picker_single", :locals => single_picker_locals - %div.form-row - %div.from-group.col-md-6 - = f.label :subscribe_notifications, "Subscribe to email notifications for new notes" - = f.check_box :subscribe_notifications - - %div.d-flex.justify-content-center - = submit_tag "Cancel", formnovalidate: "formnovalidate", class: "btn btn-secondary mx-1 col-2" - = submit_tag button_text, class: "btn btn-primary mx-1 col-2" + }); \ No newline at end of file diff --git a/app/views/ontologies/_submission_location_form.html.haml b/app/views/ontologies/_submission_location_form.html.haml new file mode 100644 index 0000000000..4fbdc2b241 --- /dev/null +++ b/app/views/ontologies/_submission_location_form.html.haml @@ -0,0 +1,49 @@ +.location-choice + %input{type: "radio", name: "submission[isRemote]", value: "3", id: "metadata_only", onchange: "displayMetadataOnlyForm()"} + %label.title{for: "metadata_only"} + Metadata only (No file) +.upload-ontology-desc.mb-2 + Allow users to view and search your ontology metadata, but not its classes and properties. +#metadata-only-form.d-none +.location-choice + %input{type: "radio", name: "submission[isRemote]", value: "1", id: "load_from_url", onchange: "displayUrlForm()"} + %label.title{for: "load_from_url"} + Load from URL +.upload-ontology-desc.mb-1 + New versions loaded on a nightly basis. +#url-form.d-none + = render Input::UrlComponent.new(label: "", name: "submission[pullLocation]") +.location-choice.mb-3.mt-3 + %input{type: "radio", name: "submission[isRemote]", value: "0", id: "upload_local_file", checked: true, onchange: "displayLocalFileForm()"} + %label.title{for: "upload_local_file"} + Upload local file +#local-file-form + = render Input::FileInputComponent.new(name: "submission[filePath]") + + +:javascript + const MetadataOnlyForm = document.getElementById("metadata-only-form"); + const UrlForm = document.getElementById("url-form"); + const LocalFileForm = document.getElementById("local-file-form"); + + const displayForm = (formElement) => { + [MetadataOnlyForm, UrlForm, LocalFileForm].forEach((form) => { + if (form === formElement) { + form.classList.remove("d-none"); + } else { + form.classList.add("d-none"); + } + }); + }; + + const displayMetadataOnlyForm = () => { + displayForm(MetadataOnlyForm); + }; + + const displayUrlForm = () => { + displayForm(UrlForm); + }; + + const displayLocalFileForm = () => { + displayForm(LocalFileForm); + }; diff --git a/app/views/ontologies/edit.html.haml b/app/views/ontologies/edit.html.haml index edaa0d6541..ef1fd0f78a 100644 --- a/app/views/ontologies/edit.html.haml +++ b/app/views/ontologies/edit.html.haml @@ -1,5 +1,5 @@ - @title = "Edit Ontology Information" %div{:style => "margin:10px;"} - = form_for :ontology, url: ontology_path(@ontology.acronym), html: {method: :put, id: "ontologyForm"} do |f| + = form_for :ontology, url: ontology_path(@ontology.acronym), html: {method: :put, id: "ontologyForm", multipart: true} do |f| = render partial: "form", locals: {f: f, button_text: "Save ontology", title_text: "Edit Ontology Information"} diff --git a/app/views/ontologies/new.html.haml b/app/views/ontologies/new.html.haml index a8bdab8e01..29a66b45e0 100644 --- a/app/views/ontologies/new.html.haml +++ b/app/views/ontologies/new.html.haml @@ -1,5 +1,5 @@ - @title = "Submit New Ontology" %div{:style => "margin:10px;"} - = form_for :ontology, url: {action: "create"}, html: {id: "ontologyForm"} do |f| - = render partial: "form", locals: {f: f} + = form_for :ontology, url: {action: "create"}, html: {id: "ontologyForm", multipart: true} do |f| + = render partial: "ontologies/form", locals: {f: f} diff --git a/app/views/ontologies_metadata_curator/_attribute_inline.html.haml b/app/views/ontologies_metadata_curator/_attribute_inline.html.haml index 58d6c02442..b430b31214 100644 --- a/app/views/ontologies_metadata_curator/_attribute_inline.html.haml +++ b/app/views/ontologies_metadata_curator/_attribute_inline.html.haml @@ -1,7 +1,7 @@ %div - if @errors %div - = render AlertMessageComponent.new(id:acronym+'-'+attribute+'-error', type: 'danger') do + = render Display::AlertComponent.new(type: 'danger') do = @errors.map{|e| e[:error]}.join(',') %div - if attribute == "contact" diff --git a/app/views/ontologies_metadata_curator/_form_edit.html.haml b/app/views/ontologies_metadata_curator/_form_edit.html.haml index 533cbde3b3..06df06d032 100644 --- a/app/views/ontologies_metadata_curator/_form_edit.html.haml +++ b/app/views/ontologies_metadata_curator/_form_edit.html.haml @@ -23,9 +23,9 @@ %a.nav-link{id: "pills-"+onto+sub_i+"-tab", "data-toggle": "pill", href: "#pills-"+onto+sub_i, class: index.zero? ? 'active' : ''} = ontology_submission_id_label(onto, sub_i) %div#change_all_warning_container - = render AlertMessageComponent.new(id:'change_all_warning', type: 'warning') do + = render Display::AlertComponent.new(type: 'warning') do %strong Apply the change for all - will update the current displayed content to all the following submssions: + will update the current displayed content to all the following submissions: - @selected_ontologies.map{|x| ontology_submission_id_label(*x)}.each do |onto| %span.badge.badge-primary = onto diff --git a/app/views/shared/_ontology_picker_single.html.erb b/app/views/shared/_ontology_picker_single.html.erb index 53e2b4f9df..1883ce9045 100644 --- a/app/views/shared/_ontology_picker_single.html.erb +++ b/app/views/shared/_ontology_picker_single.html.erb @@ -25,6 +25,6 @@
    - <%= select object_name, field_name, @onts_for_select, { :include_blank => true, :selected => selected }, :id => picker_id, :class => "ontology_picker_single form-control", "data-placeholder".to_sym => placeholder, disabled: disabled %> + <%= render Input::SelectComponent.new(label: placeholder, name: "#{object_name}[#{field_name}]", value: @onts_for_select, selected: selected) %>
    diff --git a/app/views/submissions/new.html.haml b/app/views/submissions/new.html.haml index d6413ca0a5..82e77242e1 100644 --- a/app/views/submissions/new.html.haml +++ b/app/views/submissions/new.html.haml @@ -1,9 +1,2 @@ -- @title = "Add new ontology submission" - -%div.py-4.py-md-5 - %h3.text-center.mb-4 - Add new submission - - if !(@submission.ontology.nil? || (@submission.ontology.is_a? String)) - %small.text-muted for #{@submission.ontology.acronym} - = form_for :submission, url: {action: "create"}, html: {id: "ontology_submission_form", multipart: true, 'data-turbo': true} do |f| - = render partial: "form", locals: {f: f, button_text: "Add submission"} += form_for :submission, url: {action: "create"}, html: {id: "ontology_submission_form", multipart: true} do |f| + = render partial: "ontologies/form", locals: {f: f, button_text: "Add submission"} \ No newline at end of file diff --git a/spec/components/alert_message_component_spec.rb b/spec/components/alert_message_component_spec.rb deleted file mode 100644 index fd5488bc07..0000000000 --- a/spec/components/alert_message_component_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe AlertMessageComponent, type: :component do - pending "add some examples to (or delete) #{__FILE__}" - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/test/components/previews/alert_message_component_preview.rb b/test/components/previews/alert_message_component_preview.rb deleted file mode 100644 index 4efb845e93..0000000000 --- a/test/components/previews/alert_message_component_preview.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AlertMessageComponentPreview < ViewComponent::Preview - - # @param message text - # @param type select [error, info, success, warning] - - def default(message: "Here we can type a success or failure message to the user", type: "info", closeable: true ) - render(AlertMessageComponent.new(message: message, type: type, closeable: closeable)) - end - -end \ No newline at end of file diff --git a/test/components/previews/display/alert_component_preview.rb b/test/components/previews/display/alert_component_preview.rb new file mode 100644 index 0000000000..9b9d355a69 --- /dev/null +++ b/test/components/previews/display/alert_component_preview.rb @@ -0,0 +1,34 @@ +class Display::AlertComponentPreview < ViewComponent::Preview + + #@param message text + #@param closable select [true, false] + def default(message: "New ontology Bioinformatics Ontology v2.0 has been uploaded. Check it out in the latest uploads section", closable: true) + render Display::AlertComponent.new(message: message, closable: closable) + end + + #@param message text + #@param closable select [true, false] + def danger(message: "Unable to delete ontology Chemistry Concepts Ontology. This ontology is associated with existing mappings. Please remove mappings before deleting", closable: true) + render Display::AlertComponent.new(message: message, closable: closable, type: "danger") + end + + #@param message text + #@param closable select [true, false] + def warning(message: "This ontology version is outdated and may contain inaccuracies. Consider using the latest version for accurate information", closable: true) + render Display::AlertComponent.new(message: message, closable: closable, type: "warning") + end + + #@param message text + #@param closable select [true, false] + def success(message: "Your ontology submission has been successfully uploaded and is now under review. You will receive an email confirmation shortly", closable: true) + render Display::AlertComponent.new(message: message, closable: closable, type: "success") + end + + + #@param message text + #@param delay number + def auto_close(message: "Your ontology submission has been successfully uploaded and is now under review. You will receive an email confirmation shortly", delay: 3000) + render Display::AlertComponent.new(message: message, closable: true, auto_close_delay: delay, type: "default") + end + +end \ No newline at end of file diff --git a/test/components/previews/layout/progress_pages_component_preview.rb b/test/components/previews/layout/progress_pages_component_preview.rb new file mode 100644 index 0000000000..afb6596de1 --- /dev/null +++ b/test/components/previews/layout/progress_pages_component_preview.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Layout::ProgressPagesComponentPreview < ViewComponent::Preview + + # @param pages_count number + def default(pages_count: 5) + render Layout::ProgressPagesComponent.new(pages_title: (pages_count || 0).times.map { |x| "page #{x}" }) do |c| + 5.times.each { |i| c.page { content_tag(:div, "page #{i}", class: "p-5 mx-5 text-center", style: 'width: 500px') } } + end + end +end