diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index 638ebf741c..641f492981 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -96,7 +96,7 @@ body { .form-field select, .form-field select option { - @apply border-slate-300 text-slate-900 sm:text-sm rounded-md focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-slate-800 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500; + @apply border-slate-300 text-slate-900 sm:text-sm rounded-md focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-slate-800 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500; } .form-field select:has(option[value=""]:checked) { @@ -313,9 +313,7 @@ body { } /*GROUPS LIST TREE*/ - .namespace-tree-container - > .namespace-list-tree - > .namespace-entry.has-children:first-child { + .namespace-tree-container > .namespace-list-tree > .namespace-entry.has-children:first-child { border-top: 0; } @@ -369,9 +367,7 @@ body { @apply border-t-2 border-slate-200 dark:border-slate-600; } - .namespace-list-tree - .namespace-list-tree - .namespace-entry:last-child::before { + .namespace-list-tree .namespace-list-tree .namespace-entry:last-child::before { height: auto; top: 30px; bottom: 0; @@ -394,9 +390,7 @@ body { @apply border-t border-slate-200 dark:border-slate-600; } - .namespace-list-tree - .namespace-entry.has-children - > .namespace-entry-contents:hover { + .namespace-list-tree .namespace-entry.has-children > .namespace-entry-contents:hover { @apply cursor-pointer border-slate-50 dark:border-slate-600 bg-slate-50 dark:bg-slate-600; } @@ -500,7 +494,7 @@ div.field_with_errors > :is(select) { display: none; } -.samples-app { +.fixed-table-component { height: calc(100vh - 50px - 32px); max-height: calc(100vh - 50px - 32px); display: flex; diff --git a/app/components/attachments/table_component.html.erb b/app/components/attachments/table_component.html.erb new file mode 100644 index 0000000000..41be761e49 --- /dev/null +++ b/app/components/attachments/table_component.html.erb @@ -0,0 +1,209 @@ +<%= render Viral::BaseComponent.new(**wrapper_arguments) do %> + <%= render Viral::BaseComponent.new(**system_arguments) do %> + + + + <% @columns.each_with_index do |column, index| %> + <%= render_cell( + tag: 'th', + scope: 'col', + classes: class_names('px-3 py-3', 'sticky left-0 min-w-56 max-w-56 z-10 bg-slate-50 dark:bg-slate-700': index.zero?, 'sticky left-56 z-10 bg-slate-50 dark:bg-slate-700': index == 1) + ) do %> + <% if index.zero? and @abilities[:select_attachments] %> + <%= check_box_tag "select-page", + title: t(:".select_page"), + "aria-label": t(:".select_page"), + data: { + action: "input->selection#togglePage", + controller: "filters", + selection_target: "selectPage", + }, + class: + "w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 focus:ring-2 dark:bg-slate-700 dark:border-slate-600" %> + <% end %> + <% if column == :id %> + <%= render Ransack::SortComponent.new( + ransack_obj: @q, + label: t(".#{column}"), + url: helpers.sorting_url(@q, "puid"), + field: "puid", + data: { + turbo_action: "replace", + }, + ) %> + <% elsif column == :byte_size || column == :filename %> + <%= render Ransack::SortComponent.new( + ransack_obj: @q, + label: t(".#{column}"), + url: helpers.sorting_url(@q, "file_blob_#{column}"), + field: "file_blob_#{column}", + data: { + turbo_action: "replace", + }, + ) %> + <% elsif column == :format || column == :type %> + <%= render Ransack::SortComponent.new( + ransack_obj: @q, + label: column, + url: + helpers.sorting_url(@q, URI.encode_www_form_component("metadata_#{column}")), + field: "metadata_#{column}", + data: { + turbo_action: "replace", + }, + ) %> + <% else %> + <%= render Ransack::SortComponent.new( + ransack_obj: @q, + label: t(".#{column}"), + url: helpers.sorting_url(@q, column), + field: column, + data: { + turbo_action: "replace", + }, + ) %> + <% end %> + <% end %> + <% end %> + <% if @renders_row_actions %> + <%= render_cell( + tag: 'th', + scope: 'col', + classes: class_names('px-3 py-3 bg-slate-50 dark:bg-slate-700 sticky right-0') + ) do %> + <%= t(".actions") %> + <% end %> + <% end %> + + + + <% @attachments.each do |attachment| %> + <%= render Viral::BaseComponent.new(**row_arguments(attachment)) do %> + <% @columns.each_with_index do |column, index| %> + <%= render_cell( + tag: index.zero? ? 'th' :'td', + scope: index.zero? ? 'row' : nil, + classes: class_names('px-3 py-3', 'sticky left-0 min-w-56 max-w-56 bg-slate-50 dark:bg-slate-700': index.zero?, 'sticky left-56 bg-slate-50 dark:bg-slate-700': index == 1) + ) do %> + <% if index.zero? && @abilities[:select_attachments] %> + <%= check_box_tag "attachment_ids[]", + attachment.id, + nil, + id: dom_id(attachment), + "aria-label": attachment.file.filename.to_s, + data: { + action: "input->selection#toggle", + selection_target: "rowSelection", + }, + class: + "w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 focus:ring-2 dark:bg-slate-700 dark:border-slate-600" %> + <% end %> + <% if column == :id %> + <%= attachment.puid %> + <% elsif column == :filename %> +
+ <%= viral_icon(name: :document_text, color: :subdued, classes: "h-6 w-6 ml-0 mr-2") %> + + <%= link_to attachment.file.filename, + rails_blob_path(attachment.file), + data: { + turbo: false, + }, + class: "text-slate-900 dark:text-slate-100 font-semibold hover:underline" %> + +
+ <% elsif column == :format || column == :type %> + <%= viral_pill( + text: attachment.metadata[column.to_s], + color: helpers.find_pill_color_for_attachment(attachment, column.to_s), + ) %> + <% elsif column == :byte_size %> + <%= number_to_human_size(attachment.file.blob.byte_size) %> + <% elsif column == :created_at%> + <%= helpers.local_time_ago(attachment.created_at) %> + <% end %> + <% end %> + <% end %> + <% if @renders_row_actions %> + <%= render_cell( + tag: 'td', + classes: class_names('px-3 py-3 sticky right-0 bg-white dark:bg-slate-800 z-5 space-x-2') + ) do %> + <% if @row_actions[:destroy] %> + <%= link_to( + t('.remove'), + namespace_project_attachment_path( + id: attachment.id + ), + data: { + turbo_stream: true, + turbo_method: :delete, + turbo_confirm: t('.delete_confirm', name: attachment.file.filename.to_s), + }, + class: + "font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer", + ) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + + <% if @abilities[:select_attachments] && !@attachments.empty? %> + + + + + + + <% end %> +
+ + <%= t(".counts.attachments") %>: + <%= @pagy.count %> + + + <%= t(".counts.selected") %>: + 0 + +
+ <% end %> + <% unless @attachments.empty? %> +
+ <%= render Viral::Pagy::LimitComponent.new(@pagy, item: t(".limit.item")) %> + <%= render Viral::Pagy::PaginationComponent.new(@pagy) %> +
+ <% end %> +
+ <%= viral_empty( + title: @empty[:title], + description: @empty[:description], + icon_name: :document_text, + ) %> +
+<% end %> diff --git a/app/components/attachments/table_component.rb b/app/components/attachments/table_component.rb new file mode 100644 index 0000000000..1dc15d2e64 --- /dev/null +++ b/app/components/attachments/table_component.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'ransack/helpers/form_helper' + +module Attachments + # Component for rendering a table of Attachments + class TableComponent < Component + include Ransack::Helpers::FormHelper + + # rubocop:disable Naming/MethodParameterName,Metrics/ParameterLists + def initialize( + attachments, + pagy, + q, + project, + row_actions: false, + abilities: {}, + empty: {}, + **system_arguments + ) + @attachments = attachments + @pagy = pagy + @q = q + @project = project + @abilities = abilities + @row_actions = row_actions + @empty = empty + @renders_row_actions = @row_actions.select { |_key, value| value }.count.positive? + @system_arguments = system_arguments + + @columns = columns + end + # rubocop:enable Naming/MethodParameterName,Metrics/ParameterLists + + def system_arguments + { tag: 'div' }.deep_merge(@system_arguments).tap do |args| + args[:id] = 'attachments-table' + args[:classes] = class_names(args[:classes], 'overflow-auto scrollbar') + if @abilities[:select_attachments] + args[:data] ||= {} + args[:data][:controller] = 'selection' + args[:data][:'selection-total-value'] = @pagy.count + args[:data][:'selection-action-link-outlet'] = '.action-link' + end + end + end + + def wrapper_arguments + { + tag: 'div', + classes: class_names('table-container flex flex-col shrink min-h-0') + } + end + + def row_arguments(attachment) + { tag: 'tr' }.tap do |args| + args[:classes] = class_names('bg-white', 'border-b', 'dark:bg-slate-800', 'dark:border-slate-700') + args[:id] = attachment.id + end + end + + def render_cell(**arguments, &) + render(Viral::BaseComponent.new(**arguments), &) + end + + private + + def columns + %i[id filename format type byte_size created_at] + end + end +end diff --git a/app/components/viral/pagy/limit_component.html.erb b/app/components/viral/pagy/limit_component.html.erb index f28a1f525c..2efe3ac21b 100644 --- a/app/components/viral/pagy/limit_component.html.erb +++ b/app/components/viral/pagy/limit_component.html.erb @@ -7,7 +7,7 @@ <%= t(".items", items: @item) %> <%= viral_dropdown(label: @pagy.limit) do |dropdown| %> <% Pagy::DEFAULT[:limits].each do |limit| %> - <% dropdown.with_item(label: limit, url: "?limit=#{limit}") %> + <% dropdown.with_item(label: limit, url: current_url_with_limit(limit)) %> <% end %> <% end %> <%== t(".summary", to: @pagy.to, from: @pagy.from, count: @pagy.count) %> diff --git a/app/components/viral/pagy/limit_component.rb b/app/components/viral/pagy/limit_component.rb index e18ddb79e7..b0c92be700 100644 --- a/app/components/viral/pagy/limit_component.rb +++ b/app/components/viral/pagy/limit_component.rb @@ -8,6 +8,21 @@ def initialize(pagy, item:) @pagy = pagy @item = item end + + def current_url_with_limit(limit) + current_url = request.original_url + if current_url.include? '?' + split_url = current_url.split('?') + base_url = split_url[0] + reconstructed_params = "?limit=#{limit}" + split_url[1].split('&').each do |param| + reconstructed_params << "&#{param}" unless param.include? 'limit=' + end + "#{base_url}#{reconstructed_params}" + else + "#{current_url}?limit=#{limit}" + end + end end end end diff --git a/app/components/viral/tabs_component.html.erb b/app/components/viral/tabs_component.html.erb index 6644785b66..a76b825e8e 100644 --- a/app/components/viral/tabs_component.html.erb +++ b/app/components/viral/tabs_component.html.erb @@ -1,4 +1,5 @@
+
<%= tab_content %>
diff --git a/app/controllers/projects/attachments_controller.rb b/app/controllers/projects/attachments_controller.rb new file mode 100644 index 0000000000..0f24c88fc9 --- /dev/null +++ b/app/controllers/projects/attachments_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module Projects + # Controller actions for Project Attachments + class AttachmentsController < Projects::ApplicationController + include Metadata + before_action :current_page + + def index + @q = @project.namespace.attachments.ransack(params[:q]) + set_default_sort + @pagy, @attachments = pagy_with_metadata_sort(@q.result) + end + + def new + authorize! @project, to: :create_attachment? + + render turbo_stream: turbo_stream.update('attachment_modal', + partial: 'new_attachment_modal', + locals: { + open: true, + attachment: Attachment.new(attachable: @project.namespace) + }), status: :ok + end + + def create + authorize! @project, to: :update_sample? + + @attachments = ::Attachments::CreateService.new(current_user, @project.namespace, attachment_params).execute + + status = if !@attachments.count.positive? + :unprocessable_entity + elsif @attachments.count(&:persisted?) == @attachments.count + :ok + else + :multi_status + end + + respond_to do |format| + format.turbo_stream do + render status:, locals: { attachment: Attachment.new(attachable: @project.namespace), + attachments: @attachments } + end + end + end + + private + + def current_page + @current_page = t(:'projects.sidebar.files') + end + + def context_crumbs + super + @context_crumbs += + [{ + name: t(:'projects.sidebar.files'), + path: namespace_project_attachments_path(@project.parent, @project) + }] + end + + def layout_fixed + super + return unless action_name == 'index' + + @fixed = false + end + + def set_default_sort + @q.sorts = 'updated_at desc' if @q.sorts.empty? + end + + def attachment_params + params.require(:attachment).permit(:attachable_id, :attachable_type, files: []) + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 3f56070d2f..89d2d14003 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -189,6 +189,68 @@ type AttachmentEdge { } input AttachmentFilter { + byte_size_blank: String + byte_size_cont: String + byte_size_cont_all: [String!] + byte_size_cont_any: [String!] + byte_size_does_not_match: String + byte_size_does_not_match_all: [String!] + byte_size_does_not_match_any: [String!] + byte_size_end: String + byte_size_end_all: [String!] + byte_size_end_any: [String!] + byte_size_eq: String + byte_size_eq_all: [String!] + byte_size_eq_any: [String!] + byte_size_false: String + byte_size_gt: String + byte_size_gt_all: [String!] + byte_size_gt_any: [String!] + byte_size_gteq: String + byte_size_gteq_all: [String!] + byte_size_gteq_any: [String!] + byte_size_i_cont: String + byte_size_i_cont_all: [String!] + byte_size_i_cont_any: [String!] + byte_size_in: [String!] + byte_size_in_all: [String!] + byte_size_in_any: [String!] + byte_size_lt: String + byte_size_lt_all: [String!] + byte_size_lt_any: [String!] + byte_size_lteq: String + byte_size_lteq_all: [String!] + byte_size_lteq_any: [String!] + byte_size_matches: String + byte_size_matches_all: [String!] + byte_size_matches_any: [String!] + byte_size_not_cont: String + byte_size_not_cont_all: [String!] + byte_size_not_cont_any: [String!] + byte_size_not_end: String + byte_size_not_end_all: [String!] + byte_size_not_end_any: [String!] + byte_size_not_eq: String + byte_size_not_eq_all: [String!] + byte_size_not_eq_any: [String!] + byte_size_not_false: String + byte_size_not_i_cont: String + byte_size_not_i_cont_all: [String!] + byte_size_not_i_cont_any: [String!] + byte_size_not_in: [String!] + byte_size_not_in_all: [String!] + byte_size_not_in_any: [String!] + byte_size_not_null: String + byte_size_not_start: String + byte_size_not_start_all: [String!] + byte_size_not_start_any: [String!] + byte_size_not_true: String + byte_size_null: String + byte_size_present: String + byte_size_start: String + byte_size_start_all: [String!] + byte_size_start_any: [String!] + byte_size_true: String created_at_blank: String created_at_cont: String created_at_cont_all: [String!] diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 9eaf91e89a..b122ee2d94 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -3,6 +3,7 @@ # entity class for Attachment class Attachment < ApplicationRecord include HasPuid + include MetadataSortable FORMAT_REGEX = { 'fasta' => /^\S+\.fn?a(sta)?(\.gz)?$/, @@ -18,6 +19,7 @@ class Attachment < ApplicationRecord has_logidze acts_as_paranoid + broadcasts_refreshes belongs_to :attachable, touch: :attachments_updated_at, polymorphic: true @@ -34,6 +36,7 @@ class Attachment < ApplicationRecord delegate :byte_size, to: :file ransack_alias :filename, :file_blob_filename + ransack_alias :byte_size, :file_blob_byte_size def self.model_prefix 'ATT' diff --git a/app/views/groups/samples/index.html.erb b/app/views/groups/samples/index.html.erb index 53f5b18953..438fcf479a 100644 --- a/app/views/groups/samples/index.html.erb +++ b/app/views/groups/samples/index.html.erb @@ -4,7 +4,7 @@ <%= turbo_frame_tag "samples_alert" %> <%= turbo_frame_tag "selected" %> -
+
<%= render Viral::PageHeaderComponent.new(title: t(:'.title'), subtitle: t(:'.subtitle', namespace_type: @group.class.model_name.human, namespace_name: @group.name)) do |component| %> <%= component.with_icon(name: "beaker", classes: "h-14 w-14 text-primary-700") %> <%= component.with_buttons do %> diff --git a/app/views/layouts/projects.html.erb b/app/views/layouts/projects.html.erb index 5a36fe4581..3117c5c37b 100644 --- a/app/views/layouts/projects.html.erb +++ b/app/views/layouts/projects.html.erb @@ -23,6 +23,14 @@ label: t(:"projects.sidebar.samples"), selected: @current_page == t(:"projects.sidebar.samples"), ) %> + <% if allowed_to?(:view_attachments?, @project) %> + <%= render section.with_item( + url: namespace_project_attachments_path(@project.parent, @project), + icon: "document_text", + label: t(:"projects.sidebar.files"), + selected: @current_page == t(:"projects.sidebar.files"), + ) %> + <% end %> <% if allowed_to?(:view_history?, @project) %> <%= render section.with_item( url: project_activity_path(@project), diff --git a/app/views/projects/attachments/_form.html.erb b/app/views/projects/attachments/_form.html.erb new file mode 100644 index 0000000000..dcac269bea --- /dev/null +++ b/app/views/projects/attachments/_form.html.erb @@ -0,0 +1,49 @@ +<%= form_with(model: attachment, url: namespace_project_attachments_path(id: @project.id)) do |form| %> + <%= form.hidden_field :attachable_id %> + <%= form.hidden_field :attachable_type %> + +
+ +
+ <%= form.label :files, + t(".files"), + class: + "block mb-2 text-sm font-medium text-slate-900 dark:text-white" %> + <%= form.file_field :files, + multiple: true, + direct_upload: true, + required: true, + data: { + "file-input-target": "input", + "attachment-upload-target": "attachmentsInput", + action: "change->file-upload#handleFileChange", + }, + class: + "block w-full mb-5 text-xs text-slate-900 border border-slate-300 rounded-lg cursor-pointer bg-slate-50 dark:text-slate-400 focus:outline-none dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400" %> + + <%= viral_alert(message: t('.files_ignored'), type: :info, data: { "file-upload-target": "alert" }, classes: "hidden") do %> +
    REPLACE
+ <% end %> +
+ +
+ <%= form.submit t(".upload"), + class: "button button--state-primary button--size-default", + data: { + "attachment-upload-target": "submitButton", + } %> +
+
+<% end %> diff --git a/app/views/projects/attachments/_new_attachment_modal.html.erb b/app/views/projects/attachments/_new_attachment_modal.html.erb new file mode 100644 index 0000000000..d7f741a432 --- /dev/null +++ b/app/views/projects/attachments/_new_attachment_modal.html.erb @@ -0,0 +1,9 @@ +<%= viral_dialog(open: open) do |dialog| %> + <% dialog.with_header(title: t(".upload_files")) %> + <% dialog.with_section do %> + <%= render partial: "form", + locals: { + attachment: attachment + } %> + <% end %> +<% end %> diff --git a/app/views/projects/attachments/_table.html.erb b/app/views/projects/attachments/_table.html.erb new file mode 100644 index 0000000000..f083ae5453 --- /dev/null +++ b/app/views/projects/attachments/_table.html.erb @@ -0,0 +1,19 @@ +<%# locals: (project:, pagy:, q:, attachments:) -%> +<%= render Attachments::TableComponent.new( + attachments, + pagy, + q, + project, + abilities: { + # Uncomment when ready to use multi-select functionality + # select_attachments: allowed_to?(:destroy_attachment?, project) + }, + row_actions: { + # Uncomment when ready to use destroy + # destroy: allowed_to?(:destroy_attachment?, project), + }, + empty: { + title: t(:".empty.title"), + description: t(:".empty.description"), + }, +) %> diff --git a/app/views/projects/attachments/create.turbo_stream.erb b/app/views/projects/attachments/create.turbo_stream.erb new file mode 100644 index 0000000000..66edfc55da --- /dev/null +++ b/app/views/projects/attachments/create.turbo_stream.erb @@ -0,0 +1,30 @@ +<% attachments&.each do |attachment| %> + <% if attachment.persisted? %> + <%= turbo_stream.append "flashes" do %> + <%= viral_flash( + type: :success, + data: t(".success", filename: attachment.file.filename), + ) %> + <% end %> + <% else %> + <%= turbo_stream.append "flashes" do %> + <%= viral_flash( + type: :error, + data: + t( + ".failure", + filename: attachment.file.filename, + errors: attachment.errors.full_messages.join("."), + ), + ) %> + <% end %> + <% end %> +<% end %> +<%= turbo_stream.update "attachment_modal", + partial: "new_attachment_modal", + locals: { + open: false, + attachment: attachment, + } %> + + diff --git a/app/views/projects/attachments/index.html.erb b/app/views/projects/attachments/index.html.erb new file mode 100644 index 0000000000..81ef9ecb95 --- /dev/null +++ b/app/views/projects/attachments/index.html.erb @@ -0,0 +1,55 @@ +<%= turbo_refreshes_with method: :morph, scroll: :preserve %> + +<%= turbo_frame_tag "attachment_modal" %> + +
+ <%= render Viral::PageHeaderComponent.new( + title: t(".title"), + subtitle: t(".subtitle", project_name: @project.name), + ) do |component| %> + <%= component.with_buttons do %> + <% if allowed_to?(:create_attachment?, @project) %> + <%= link_to t(".upload_files"), + new_namespace_project_attachment_path(id: @project.id), + data: { + turbo_frame: "attachment_modal", + turbo_stream: true, + }, + class: "button button--size-default button--state-default" %> + <% end %> + <% end %> + <% end %> + +
+
+ <%= search_form_for @q, url: namespace_project_attachments_path(@project.parent, @project), html: { "data-controller": "filters" } do |f| %> + <%= hidden_field_tag :limit, @pagy.limit %> + <%= f.hidden_field :s, value: "#{@q.sorts[0].name} #{@q.sorts[0].dir}" %> + <%= f.hidden_field :format, value: "turbo_stream" %> + + <%= f.label :name_cont, "SEARCH", class: "sr-only" %> +
+
+ <%= viral_icon(name: "magnifying_glass", classes: "h-5 w-5") %> +
+ <%= f.search_field :puid_or_file_blob_filename_cont, + "data-action": "filters#submit", + class: + "block w-full p-2.5 pl-10 text-sm text-slate-900 border border-slate-300 rounded-lg bg-slate-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500", + placeholder: t(:".search.placeholder") %> +
+ <% end %> +
+
+ <%= render partial: "table", + locals: { + attachments: @attachments, + pagy: @pagy, + q: @q, + project: @project + } %> +
diff --git a/app/views/projects/samples/attachments/create.turbo_stream.erb b/app/views/projects/samples/attachments/create.turbo_stream.erb index d2922e33e6..6735d0825c 100644 --- a/app/views/projects/samples/attachments/create.turbo_stream.erb +++ b/app/views/projects/samples/attachments/create.turbo_stream.erb @@ -3,14 +3,14 @@ <%= turbo_stream.append "flashes" do %> <%= viral_flash( type: :success, - data: t(".success", filename: attachment.file.filename) + data: t(".success", filename: attachment.file.filename), ) %> <% end %> <% if !attachment.associated_attachment || (attachment.associated_attachment && attachment.metadata['direction'] == 'forward') %> <%= turbo_stream.append "attachments-table-body", partial: "attachment", locals: { - attachment: attachment + attachment: attachment, } %> <% end %> <% else %> @@ -21,8 +21,8 @@ t( ".failure", filename: attachment.file.filename, - errors: attachment.errors.full_messages.join(".") - ) + errors: attachment.errors.full_messages.join("."), + ), ) %> <% end %> <% end %> @@ -31,5 +31,5 @@ partial: "new_attachment_modal", locals: { open: false, - attachment: attachment + attachment: attachment, } %> diff --git a/app/views/projects/samples/index.html.erb b/app/views/projects/samples/index.html.erb index b52ec69081..c1df497ab3 100644 --- a/app/views/projects/samples/index.html.erb +++ b/app/views/projects/samples/index.html.erb @@ -6,7 +6,7 @@ <%= turbo_stream_from @project %> -
+
<%= render Viral::PageHeaderComponent.new(title: t('.title')) do |component| %> <%= component.with_icon(name: "beaker", classes: "h-14 w-14 text-primary-700") %> <%= component.with_buttons do %> diff --git a/app/views/projects/samples/show.html.erb b/app/views/projects/samples/show.html.erb index 613fbd7996..512cd95e3c 100644 --- a/app/views/projects/samples/show.html.erb +++ b/app/views/projects/samples/show.html.erb @@ -28,7 +28,7 @@ <% end %>
-