-
Notifications
You must be signed in to change notification settings - Fork 24
Add inline gallery controls to set featured image and remove images #1545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| class GalleryAssetsController < ApplicationController | ||
| before_action :set_asset, only: [ :destroy, :make_primary ] | ||
|
|
||
| # Remove an image from the gallery | ||
| def destroy | ||
| authorize! @asset.owner, to: :manage? # check the policy of the record that owns the asset | ||
| @asset.destroy! | ||
|
|
||
| redirect_back_or_to polymorphic_path(@asset.owner), notice: "Image removed." | ||
| end | ||
|
|
||
| # Promote an existing gallery image to be the featured (primary) image | ||
| def make_primary | ||
| authorize! @asset.owner, to: :manage? | ||
| ActiveRecord::Base.transaction do | ||
| if existing_primary = @asset.owner.assets.find_by(type: "PrimaryAsset") | ||
| existing_primary.update!(type: "GalleryAsset") | ||
| end | ||
|
|
||
| @asset.update!(type: "PrimaryAsset") | ||
| end | ||
|
|
||
| redirect_back_or_to polymorphic_path(@asset.owner), notice: "Featured image updated." | ||
| end | ||
|
Comment on lines
+13
to
+24
|
||
|
|
||
| private | ||
|
|
||
| def set_asset | ||
| @asset = GalleryAsset.find(params[:id]) | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,18 @@ | |
| <% gallery_assets = resource.gallery_assets.select { |img| img.file.attached? } %> | ||
| <% align = (defined?(align) && align.present?) ? align.to_s : "center" %> | ||
| <% if gallery_assets.any? %> | ||
| <% can_manage = allowed_to?(:manage?, resource) %> | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Controls are gated by |
||
| <div class="<%= resource.class.table_name %>-gallery text-<%= align %> mb-4"> | ||
| <div class="flex flex-wrap justify-<%= align %> gap-4 <%= 'mx-auto' if align == 'center' %> max-w-4xl"> | ||
| <% gallery_assets.each_with_index do |gallery_assets, idx| %> | ||
| <%= render "assets/display_image", item: gallery_assets, idx: idx, variant: :gallery, link: (defined?(link) ? link : nil) %> | ||
| <% gallery_assets.each_with_index do |gallery_asset, idx| %> | ||
| <% if can_manage %> | ||
| <div class="relative group"> | ||
| <%= render "assets/display_image", item: gallery_asset, idx: idx, variant: :gallery, link: (defined?(link) ? link : nil) %> | ||
| <%= render "assets/gallery_item_controls", asset: gallery_asset %> | ||
| </div> | ||
| <% else %> | ||
| <%= render "assets/display_image", item: gallery_asset, idx: idx, variant: :gallery, link: (defined?(link) ? link : nil) %> | ||
| <% end %> | ||
| <% end %> | ||
| </div> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <div class="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition"> | ||
| <%= button_to make_primary_gallery_asset_path(asset), | ||
| method: :post, | ||
| class: "bg-white text-gray-700 rounded-full w-8 h-8 flex items-center justify-center shadow hover:bg-gray-100 transition", | ||
| title: "Set as featured image", | ||
| data: { turbo_confirm: "Make this the featured image?" } do %> | ||
| <i class="fa-solid fa-star"></i> | ||
| <% end %> | ||
| <%= button_to gallery_asset_path(asset), | ||
| method: :delete, | ||
| class: "bg-white text-red-600 rounded-full w-8 h-8 flex items-center justify-center shadow hover:bg-gray-100 transition", | ||
| title: "Remove image", | ||
| data: { turbo_confirm: "Remove this image from the gallery?" } do %> | ||
|
Comment on lines
+2
to
+13
|
||
| <i class="fa-solid fa-trash"></i> | ||
| <% end %> | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,11 @@ | |
| # end | ||
| resources :primary_assets | ||
| resources :rich_text_assets | ||
| resources :gallery_assets, only: [ :destroy ] do | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Routed under |
||
| member do | ||
| post :make_primary | ||
| end | ||
| end | ||
|
|
||
| namespace :images do | ||
| resources :primary_images, only: [ :show ] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| require "rails_helper" | ||
|
|
||
| RSpec.describe "/gallery_assets", type: :request do | ||
| let(:admin) { create(:user, :admin) } | ||
| let(:regular_user) { create(:user) } | ||
| let(:story) { create(:story) } | ||
|
|
||
| describe "DELETE /gallery_assets/:id" do | ||
| let!(:gallery_asset) { create(:gallery_asset, :with_file, owner: story) } | ||
|
|
||
| context "as admin" do | ||
| before { sign_in admin } | ||
|
|
||
| it "removes the gallery image" do | ||
| expect { | ||
| delete gallery_asset_url(gallery_asset) | ||
| }.to change(Asset, :count).by(-1) | ||
|
|
||
| expect(response).to redirect_to(story_url(story)) | ||
| expect(Asset.exists?(gallery_asset.id)).to be(false) | ||
| end | ||
| end | ||
|
|
||
| context "as a non-admin user" do | ||
| before { sign_in regular_user } | ||
|
|
||
| it "does not remove the image" do | ||
| expect { | ||
| delete gallery_asset_url(gallery_asset) | ||
| }.not_to change(Asset, :count) | ||
| end | ||
| end | ||
|
|
||
| context "when signed out" do | ||
| it "does not remove the image" do | ||
| expect { | ||
| delete gallery_asset_url(gallery_asset) | ||
| }.not_to change(Asset, :count) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| describe "POST /gallery_assets/:id/make_primary" do | ||
| let!(:gallery_asset) { create(:gallery_asset, :with_file, owner: story) } | ||
|
|
||
| context "as admin" do | ||
| before { sign_in admin } | ||
|
|
||
| it "promotes the gallery image to the featured image" do | ||
| post make_primary_gallery_asset_url(gallery_asset) | ||
|
|
||
| expect(response).to redirect_to(story_url(story)) | ||
| expect(Asset.find(gallery_asset.id).type).to eq("PrimaryAsset") | ||
| end | ||
|
|
||
| it "demotes the existing featured image to the gallery" do | ||
| existing_primary = create(:primary_asset, :with_file, owner: story) | ||
|
|
||
| post make_primary_gallery_asset_url(gallery_asset) | ||
|
|
||
| expect(Asset.find(existing_primary.id).type).to eq("GalleryAsset") | ||
| expect(Asset.find(gallery_asset.id).type).to eq("PrimaryAsset") | ||
| expect(story.assets.where(type: "PrimaryAsset").count).to eq(1) | ||
| end | ||
| end | ||
|
|
||
| context "as a non-admin user" do | ||
| before { sign_in regular_user } | ||
|
|
||
| it "does not change the asset type" do | ||
| post make_primary_gallery_asset_url(gallery_asset) | ||
|
|
||
| expect(Asset.find(gallery_asset.id).type).to eq("GalleryAsset") | ||
| end | ||
| end | ||
| end | ||
|
Comment on lines
+67
to
+76
|
||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Promotion is a type swap, not a new upload: the current
PrimaryAssetis demoted back toGalleryAssetand the chosen gallery image becomes thePrimaryAsset. This mirrors the existingPrimaryAssetsController#createflow used by the "Change photo" picker, so there's a single definition of "featured".