From db6f2c6e84b9f8c713eb11e5d67a665cfaa65577 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 11 Aug 2025 20:12:46 -0230 Subject: [PATCH 1/2] feat: add uploads gallery with search and copy --- .../better_together/uploads_controller.js | 37 ++++++++++++++++ .../better_together/uploads/index.html.erb | 43 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 app/javascript/controllers/better_together/uploads_controller.js diff --git a/app/javascript/controllers/better_together/uploads_controller.js b/app/javascript/controllers/better_together/uploads_controller.js new file mode 100644 index 000000000..8be2b247e --- /dev/null +++ b/app/javascript/controllers/better_together/uploads_controller.js @@ -0,0 +1,37 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["search", "sort", "list", "item"] + + filter() { + const query = this.searchTarget.value.toLowerCase() + this.itemTargets.forEach((item) => { + const name = item.dataset.name.toLowerCase() + item.classList.toggle("d-none", !name.includes(query)) + }) + } + + sort() { + const key = this.sortTarget.value + const items = this.itemTargets.slice().sort((a, b) => { + if (key === "name") { + return a.dataset.name.localeCompare(b.dataset.name) + } + if (key === "created_at") { + return new Date(b.dataset.createdAt) - new Date(a.dataset.createdAt) + } + return 0 + }) + items.forEach((item) => this.listTarget.appendChild(item)) + } + + copy(event) { + const url = event.currentTarget.dataset.url + navigator.clipboard?.writeText(url) + } + + insert(event) { + const signedId = event.currentTarget.dataset.signedId + this.dispatch("insert", { detail: { signedId } }) + } +} diff --git a/app/views/better_together/uploads/index.html.erb b/app/views/better_together/uploads/index.html.erb index b10c9114b..19a5cc416 100644 --- a/app/views/better_together/uploads/index.html.erb +++ b/app/views/better_together/uploads/index.html.erb @@ -1,2 +1,41 @@ -

Files#index

-

Find me in app/views/better_together/files/index.html.erb

+<% content_for :page_title do %> + <%= BetterTogether::Upload.model_name.human.pluralize %> +<% end %> + +
+
+
+ +
+
+ +
+
+ +
+ <% @uploads.each do |upload| %> +
+
+ <% if upload.file.attached? %> + <%= image_tag upload.file.variant(resize_to_limit: [200, 200]), class: "card-img-top", alt: upload.name %> + <% end %> +
+
<%= upload.name %>
+
    +
  • Filename: <%= upload.filename %>
  • +
  • Size: <%= number_to_human_size(upload.byte_size) %>
  • +
  • Type: <%= upload.content_type %>
  • +
+
+ + +
+
+
+
+ <% end %> +
+
From 959144973af2fbc1b90ffa8f0c4f5dbbb3c5d056 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 12 Aug 2025 13:12:36 -0230 Subject: [PATCH 2/2] test: add uploads gallery feature spec --- spec/features/uploads/gallery_spec.rb | 47 +++++++++++++++++++++++++++ spec/support/capybara.rb | 5 ++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 spec/features/uploads/gallery_spec.rb diff --git a/spec/features/uploads/gallery_spec.rb b/spec/features/uploads/gallery_spec.rb new file mode 100644 index 000000000..94ed44a38 --- /dev/null +++ b/spec/features/uploads/gallery_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'uploads gallery', type: :feature, js: true do # rubocop:disable Metrics/BlockLength + include BetterTogether::DeviseSessionHelpers + + before do + configure_host_platform + login_as_platform_manager + @creator = BetterTogether::User.find_by(email: 'manager@example.test').person + end + + def create_upload(name, creator:, created_at: Time.current) + create(:better_together_upload, name:, creator:, created_at:).tap do |upload| + upload.file.attach(io: StringIO.new('stub'), filename: "#{name}.txt", content_type: 'text/plain') + end + end + + scenario 'searches, sorts, and copies upload urls' do + create_upload('Alpha', creator: @creator, created_at: 2.days.ago) + create_upload('Beta', creator: @creator, created_at: 1.day.ago) + + visit file_index_path(locale: I18n.default_locale) + + expect(page.all('[data-better_together--uploads-target="item"] .card-title').map(&:text)) + .to eq(%w[Beta Alpha]) + + fill_in placeholder: 'Search uploads', with: 'Al' + expect(page).to have_selector('[data-name="Alpha"]', visible: true) + expect(page).to have_selector('[data-name="Beta"].d-none', visible: :all) + + find('select[data-better_together--uploads-target="sort"]').select('Name') + expect(page.all('[data-better_together--uploads-target="item"] .card-title').map(&:text)) + .to eq(%w[Alpha Beta]) + + copy_button = find('button', text: 'Copy URL', match: :first) + page.execute_script <<~JS + window.copiedText = null; + Object.defineProperty(navigator, "clipboard", { + value: { writeText: function(t){ window.copiedText = t } } + }); + JS + copy_button.click + expect(page.evaluate_script('window.copiedText')).to eq(copy_button['data-url']) + end +end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index f2b1c2b99..260aafd2e 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -17,11 +17,14 @@ ) # Generate a unique temporary directory for each session to avoid conflicts options.add_argument("--user-data-dir=#{Dir.mktmpdir}") + options.binary = '/usr/bin/chromium-browser' + service = Selenium::WebDriver::Service.chrome(path: '/usr/bin/chromedriver') Capybara::Selenium::Driver.new( app, browser: :chrome, - options: options + options: options, + service: service ) end