From b935a0e8d9ea2268fc6ab67ba8011f02eac2ee46 Mon Sep 17 00:00:00 2001 From: David Cook Date: Wed, 22 May 2024 15:07:16 +1000 Subject: [PATCH 1/6] [add gem] turbo-rails This gives us a nice 'turbo_stream' format helper. --- Gemfile | 2 ++ Gemfile.lock | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Gemfile b/Gemfile index e9adc0d0b3..3d2eb53489 100644 --- a/Gemfile +++ b/Gemfile @@ -105,6 +105,8 @@ gem 'sidekiq-scheduler' gem "cable_ready", "5.0.1" gem "stimulus_reflex", "3.5.0.rc3" +gem "turbo-rails" + gem 'combine_pdf' gem 'wicked_pdf' gem 'wkhtmltopdf-binary' diff --git a/Gemfile.lock b/Gemfile.lock index bdc08a2fdd..7bda8159e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -772,6 +772,10 @@ GEM timecop (0.9.8) timeout (0.4.1) ttfunk (1.7.0) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -960,6 +964,7 @@ DEPENDENCIES stringex (~> 2.8.5) stripe timecop + turbo-rails valid_email2 validates_lengths_from_database vcr From 3fcc9ac1fadd97d289752df9548a1067995be9bd Mon Sep 17 00:00:00 2001 From: David Cook Date: Wed, 22 May 2024 15:14:26 +1000 Subject: [PATCH 2/6] Move product image edit modal to Turbo Stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Didn't even need to touch the controller, because data-turbo-stream tells it to render the turbo_stream format ✨ But you might notice that it doesn't redirect to the right return_url yet. Let's see if we can use more Turbo to avoid the page refresh.. TODO: also handle empty images --- app/reflexes/products_reflex.rb | 12 ------------ .../admin/products_v3/_edit_image.html.haml | 17 ----------------- .../admin/products_v3/_product_row.html.haml | 9 +++++---- app/views/admin/products_v3/index.html.haml | 2 +- .../spree/admin/images/edit.turbo_stream.haml | 18 ++++++++++++++++++ config/locales/en.yml | 9 +++++---- 6 files changed, 29 insertions(+), 38 deletions(-) delete mode 100644 app/views/admin/products_v3/_edit_image.html.haml create mode 100644 app/views/spree/admin/images/edit.turbo_stream.haml diff --git a/app/reflexes/products_reflex.rb b/app/reflexes/products_reflex.rb index 74a0b49873..4c8aabc0d2 100644 --- a/app/reflexes/products_reflex.rb +++ b/app/reflexes/products_reflex.rb @@ -49,18 +49,6 @@ class ProductsReflex < ApplicationReflex fetch_and_render_products_with_flash end - def edit_image - id = current_id_from_element(element) - product = product_finder(id).find_product - image = product.image - - image = Spree::Image.new(viewable: product) if product.image.blank? - - morph "#modal-component", - render(partial: "admin/products_v3/edit_image", - locals: { product:, image:, return_url: url }) - end - private def init_filters_params diff --git a/app/views/admin/products_v3/_edit_image.html.haml b/app/views/admin/products_v3/_edit_image.html.haml deleted file mode 100644 index 57fccaac76..0000000000 --- a/app/views/admin/products_v3/_edit_image.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -= render ModalComponent.new id: "#modal_edit_product_image_#{image.id}", instant: true, close_button: false, modal_class: :fit do - %h2= t(".title") - - -# Display image in the same way it appears in the shopfront popup - %p= image_tag image.persisted? ? image.url(:large) : Spree::Image.default_image_url(:large), width: 433, height: 433 - - -# Submit to controller, because StimulusReflex doesn't support file uploads - = form_for [:admin, product, image], - html: { multipart: true }, data: { controller: "form" } do |f| - %input{ type: :hidden, name: :return_url, value: return_url} - = f.hidden_field :viewable_id, value: product.id - - .modal-actions.justify-end - %input{ class: "secondary relaxed", type: 'button', value: t('.close'), "data-action": "click->modal#close" } - -# label.button provides a handy shortcut to open the file selector on click. Unfortunately this trick isn't keyboard accessible though.. - = f.label :attachment, t(".upload"), class: "button primary relaxed icon-upload-alt" - = f.file_field :attachment, accept: "image/*", style: "display: none", "data-action": "change->form#submit" diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index f82ef26933..5cd0cc8db1 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -1,7 +1,8 @@ -%td.with-image - %a.image-field{ href: admin_product_images_path(product), data: { controller: "modal", reflex: "click->products#edit_image", "current-id": product.id} } - = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 - .button.secondary.mini= t('admin.products_page.image.edit') +%td.with-image{ id: "image-#{product.id}" } + - if product.image.present? # todo: handle blank img + %a.image-field{ href: edit_admin_product_image_path(product.id, product.image.id), 'data-turbo-stream': true } + = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 + .button.secondary.mini= t('admin.products_page.image.edit') %td.field.align-left.header.naked_inputs = f.hidden_field :id = f.text_field :name, 'aria-label': t('admin.products_page.columns.name') diff --git a/app/views/admin/products_v3/index.html.haml b/app/views/admin/products_v3/index.html.haml index 5087feddc2..668a8527a4 100644 --- a/app/views/admin/products_v3/index.html.haml +++ b/app/views/admin/products_v3/index.html.haml @@ -19,4 +19,4 @@ - %w[product variant].each do |object_type| = render partial: 'delete_modal', locals: { object_type: } #modal-component - + #edit_image_modal diff --git a/app/views/spree/admin/images/edit.turbo_stream.haml b/app/views/spree/admin/images/edit.turbo_stream.haml new file mode 100644 index 0000000000..4cadb68988 --- /dev/null +++ b/app/views/spree/admin/images/edit.turbo_stream.haml @@ -0,0 +1,18 @@ += turbo_stream.update "edit_image_modal" do + = render ModalComponent.new id: "#modal_edit_product_image", instant: true, close_button: false, modal_class: :fit do + %h2= t(".title") + + -# Display image in the same way it appears in the shopfront popup + %p= image_tag @image.persisted? ? @image.url(:large) : Spree::Image.default_image_url(:large), width: 433, height: 433 + + -# Submit to controller + = form_for [:admin, @product, @image], + html: { multipart: true }, data: { controller: "form" } do |f| + %input{ type: :hidden, name: :return_url, value: request.referer } + = f.hidden_field :viewable_id, value: @product.id + + .modal-actions.justify-end + %input{ class: "secondary relaxed", type: 'button', value: t('.close'), "data-action": "click->modal#close" } + -# label.button provides a handy shortcut to open the file selector on click. Unfortunately this trick isn't keyboard accessible though.. + = f.label :attachment, t(".upload"), class: "button primary relaxed icon-upload-alt" + = f.file_field :attachment, accept: "image/*", style: "display: none", "data-action": "change->form#submit" diff --git a/config/locales/en.yml b/config/locales/en.yml index b6d63a5632..a705c300b8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -899,10 +899,6 @@ en: new_variant: New variant bulk_update: success: Changes saved - edit_image: - title: Edit product photo - close: Back - upload: Upload photo delete_product: success: Successfully deleted the product error: Unable to delete the product @@ -4148,6 +4144,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using must_be_int: "must be an integer" admin: + images: + edit: + title: Edit product photo + close: Back + upload: Upload photo mail_methods: send_testmail: "Send test email" testmail: From 536b5608ab5aa51363b6ef0c5fe12cefcb8e2ccf Mon Sep 17 00:00:00 2001 From: David Cook Date: Wed, 22 May 2024 16:54:41 +1000 Subject: [PATCH 3/6] Show edit form for new images --- app/controllers/admin/products_v3_controller.rb | 2 ++ app/controllers/spree/admin/images_controller.rb | 5 ++++- app/helpers/admin/products_helper.rb | 13 +++++++++++++ app/views/admin/products_v3/_product_row.html.haml | 7 +++---- 4 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 app/helpers/admin/products_helper.rb diff --git a/app/controllers/admin/products_v3_controller.rb b/app/controllers/admin/products_v3_controller.rb index f8cdc339c2..77098ee7bb 100644 --- a/app/controllers/admin/products_v3_controller.rb +++ b/app/controllers/admin/products_v3_controller.rb @@ -2,6 +2,8 @@ module Admin class ProductsV3Controller < Spree::Admin::BaseController + helper ProductsHelper + before_action :init_filters_params before_action :init_pagination_params diff --git a/app/controllers/spree/admin/images_controller.rb b/app/controllers/spree/admin/images_controller.rb index 0e0890a229..28ef28bbcb 100644 --- a/app/controllers/spree/admin/images_controller.rb +++ b/app/controllers/spree/admin/images_controller.rb @@ -17,7 +17,10 @@ module Spree def new @url_filters = ::ProductFilters.new.extract(request.query_parameters) - render layout: !request.xhr? + respond_with do |format| + format.turbo_stream { render :edit } + format.all { render layout: !request.xhr? } + end end def edit diff --git a/app/helpers/admin/products_helper.rb b/app/helpers/admin/products_helper.rb new file mode 100644 index 0000000000..e29b378342 --- /dev/null +++ b/app/helpers/admin/products_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Admin + module ProductsHelper + def product_image_form_path(product) + if product.image.present? + edit_admin_product_image_path(product.id, product.image.id) + else + new_admin_product_image_path(product.id) + end + end + end +end diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index 5cd0cc8db1..706a3ec7ed 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -1,8 +1,7 @@ %td.with-image{ id: "image-#{product.id}" } - - if product.image.present? # todo: handle blank img - %a.image-field{ href: edit_admin_product_image_path(product.id, product.image.id), 'data-turbo-stream': true } - = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 - .button.secondary.mini= t('admin.products_page.image.edit') + %a.image-field{ href: product_image_form_path(product), 'data-turbo-stream': true } + = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 + .button.secondary.mini= t('admin.products_page.image.edit') %td.field.align-left.header.naked_inputs = f.hidden_field :id = f.text_field :name, 'aria-label': t('admin.products_page.columns.name') From 665273ce2fd383b18efda9609bace313063a2df0 Mon Sep 17 00:00:00 2001 From: David Cook Date: Wed, 22 May 2024 16:29:02 +1000 Subject: [PATCH 4/6] [wip] Attempt to update edited image in-place with Turbo Stream but it doesn't quite work. Maybe we can force it with JS (https://www.writesoftwarewell.com/process-turbo-stream-javascript/) --- app/controllers/spree/admin/images_controller.rb | 8 +++++++- app/views/admin/products_v3/_product_image.html.haml | 3 +++ app/views/admin/products_v3/_product_row.html.haml | 4 +--- app/views/spree/admin/images/edit.turbo_stream.haml | 6 +++--- app/views/spree/admin/images/update.turbo_stream.haml | 2 ++ 5 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 app/views/admin/products_v3/_product_image.html.haml create mode 100644 app/views/spree/admin/images/update.turbo_stream.haml diff --git a/app/controllers/spree/admin/images_controller.rb b/app/controllers/spree/admin/images_controller.rb index 28ef28bbcb..6084972102 100644 --- a/app/controllers/spree/admin/images_controller.rb +++ b/app/controllers/spree/admin/images_controller.rb @@ -3,6 +3,8 @@ module Spree module Admin class ImagesController < ::Admin::ResourceController + helper ::Admin::ProductsHelper + # This will make resource controller redirect correctly after deleting product images. # This can be removed after upgrading to Spree 2.1. # See here https://github.com/spree/spree/commit/334a011d2b8e16355e4ae77ae07cd93f7cbc8fd1 @@ -50,7 +52,11 @@ module Spree if @object.update(permitted_resource_params) flash[:success] = flash_message_for(@object, :successfully_updated) - redirect_to location_after_save + + respond_with do |format| + format.html { redirect_to location_after_save } + format.turbo_stream + end else respond_with(@object) end diff --git a/app/views/admin/products_v3/_product_image.html.haml b/app/views/admin/products_v3/_product_image.html.haml new file mode 100644 index 0000000000..cc3e77d141 --- /dev/null +++ b/app/views/admin/products_v3/_product_image.html.haml @@ -0,0 +1,3 @@ +%a.image-field{ href: product_image_form_path(product), 'data-turbo-stream': true } + = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 + .button.secondary.mini= t('admin.products_page.image.edit') diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index 706a3ec7ed..0908652009 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -1,7 +1,5 @@ %td.with-image{ id: "image-#{product.id}" } - %a.image-field{ href: product_image_form_path(product), 'data-turbo-stream': true } - = image_tag product.image&.url(:mini) || Spree::Image.default_image_url(:mini), width: 40, height: 40 - .button.secondary.mini= t('admin.products_page.image.edit') + = render partial: "product_image", locals: { product: } %td.field.align-left.header.naked_inputs = f.hidden_field :id = f.text_field :name, 'aria-label': t('admin.products_page.columns.name') diff --git a/app/views/spree/admin/images/edit.turbo_stream.haml b/app/views/spree/admin/images/edit.turbo_stream.haml index 4cadb68988..89dbb89bdf 100644 --- a/app/views/spree/admin/images/edit.turbo_stream.haml +++ b/app/views/spree/admin/images/edit.turbo_stream.haml @@ -5,9 +5,9 @@ -# Display image in the same way it appears in the shopfront popup %p= image_tag @image.persisted? ? @image.url(:large) : Spree::Image.default_image_url(:large), width: 433, height: 433 - -# Submit to controller + -# Submit as turbo stream to avoid full page reload. But turbo is ignoring it??!! = form_for [:admin, @product, @image], - html: { multipart: true }, data: { controller: "form" } do |f| + html: { multipart: true }, data: { controller: "form", 'turbo-stream': true } do |f| %input{ type: :hidden, name: :return_url, value: request.referer } = f.hidden_field :viewable_id, value: @product.id @@ -15,4 +15,4 @@ %input{ class: "secondary relaxed", type: 'button', value: t('.close'), "data-action": "click->modal#close" } -# label.button provides a handy shortcut to open the file selector on click. Unfortunately this trick isn't keyboard accessible though.. = f.label :attachment, t(".upload"), class: "button primary relaxed icon-upload-alt" - = f.file_field :attachment, accept: "image/*", style: "display: none", "data-action": "change->form#submit" + = f.file_field :attachment, accept: "image/*", style: "display: none", "data-action": "change->form#submit change->modal#close" diff --git a/app/views/spree/admin/images/update.turbo_stream.haml b/app/views/spree/admin/images/update.turbo_stream.haml new file mode 100644 index 0000000000..88638b3403 --- /dev/null +++ b/app/views/spree/admin/images/update.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update "image-#{@product.id}" do + = render partial: "admin/products_v3/product_image", locals: { product: @product } From 05f0b935487fd16f3882e1861f84283e4ae81598 Mon Sep 17 00:00:00 2001 From: David Cook Date: Thu, 23 May 2024 11:23:26 +1000 Subject: [PATCH 5/6] Use requestSubmit to allow JS events Yay, now it works. Not sure the best way to show loading yet. - currently the Turbo loading indicator shows which is better than nothing (blue bar at top) - ideally we could show a small spinner over the image thumbnail. need to write some stimulus to hook into turbo lifecycle I guess. - or we could activate the frame-level loading overlay. refactor loading_controller a bit so that it's applied on the container, then hopefully we can just call change->loading#showLoading. the turbo_stream response could dectivate it. --- app/views/spree/admin/images/edit.turbo_stream.haml | 5 +++-- app/webpacker/controllers/form_controller.js | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/spree/admin/images/edit.turbo_stream.haml b/app/views/spree/admin/images/edit.turbo_stream.haml index 89dbb89bdf..d2ffe7dd53 100644 --- a/app/views/spree/admin/images/edit.turbo_stream.haml +++ b/app/views/spree/admin/images/edit.turbo_stream.haml @@ -5,9 +5,10 @@ -# Display image in the same way it appears in the shopfront popup %p= image_tag @image.persisted? ? @image.url(:large) : Spree::Image.default_image_url(:large), width: 433, height: 433 - -# Submit as turbo stream to avoid full page reload. But turbo is ignoring it??!! + -# Submit as turbo stream to avoid full page reload. + -# TODO: show loading indicator. = form_for [:admin, @product, @image], - html: { multipart: true }, data: { controller: "form", 'turbo-stream': true } do |f| + html: { multipart: true }, data: { controller: "form" } do |f| %input{ type: :hidden, name: :return_url, value: request.referer } = f.hidden_field :viewable_id, value: @product.id diff --git a/app/webpacker/controllers/form_controller.js b/app/webpacker/controllers/form_controller.js index aaac9db4ec..54354d340f 100644 --- a/app/webpacker/controllers/form_controller.js +++ b/app/webpacker/controllers/form_controller.js @@ -2,6 +2,8 @@ import { Controller } from "stimulus"; export default class FormController extends Controller { submit() { - this.element.submit(); + // Validate and submit the form, using the default submit button. Raises JS events. + // Ref: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit + this.element.requestSubmit(); } } From 55ac816a87898d23427fdace10c1cd757dfeba5b Mon Sep 17 00:00:00 2001 From: David Cook Date: Thu, 23 May 2024 11:50:15 +1000 Subject: [PATCH 6/6] Show success message Phew, that was really easy. And now the existing feature spec still Just Works. --- app/views/spree/admin/images/update.turbo_stream.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/spree/admin/images/update.turbo_stream.haml b/app/views/spree/admin/images/update.turbo_stream.haml index 88638b3403..169bf5c89f 100644 --- a/app/views/spree/admin/images/update.turbo_stream.haml +++ b/app/views/spree/admin/images/update.turbo_stream.haml @@ -1,2 +1,3 @@ = turbo_stream.update "image-#{@product.id}" do = render partial: "admin/products_v3/product_image", locals: { product: @product } + = render partial: "admin/shared/flashes", locals: { flashes: flash } if defined? flash