diff --git a/app/controllers/avo/actions_controller.rb b/app/controllers/avo/actions_controller.rb index bdec1d4047..90b2c1e683 100644 --- a/app/controllers/avo/actions_controller.rb +++ b/app/controllers/avo/actions_controller.rb @@ -30,7 +30,8 @@ def handle (resource_ids.any? ? @resource.find_record(resource_ids, params: params) : []) ) - respond performed_action.response + @response = performed_action.response + respond end private @@ -55,44 +56,42 @@ def action_class end end - def respond(response) - messages = get_messages response - return keep_modal_open(messages) if response[:keep_modal_open] - - response[:type] ||= :reload - - if response[:type] == :download - return send_data response[:path], filename: response[:filename] - end + def respond + # Flash the messages collected from the action + flash_messages respond_to do |format| format.turbo_stream do - # Flash the messages collected from the action - flash_messages messages - - if response[:type] == :redirect - render turbo_stream: turbo_stream.redirect_to( - Avo::ExecutionContext.new(target: response[:path]).handle, - nil, - response[:redirect_args][:turbo_frame], - **response[:redirect_args].except(:turbo_frame) - ) - else - redirect_back fallback_location: resources_path(resource: @resource) + case @response[:type] + when :keep_modal_open # Only render the flash messages if the action keeps the modal open + render partial: "avo/partials/flash_alerts" + when :download # Check 'app/views/avo/actions/download.turbo_stream.erb' for more information. + render "avo/actions/download" + when :redirect # Turbo redirect to the path + render turbo_stream: turbo_stream.redirect_to( + Avo::ExecutionContext.new( + target: @response[:path] + ).handle, + nil, + @response[:redirect_args][:turbo_frame], + **@response[:redirect_args].except(:turbo_frame) + ) + else # Reload the page + redirect_back fallback_location: resources_path(resource: @resource) end end end end - def get_messages(response) + def get_messages default_message = { type: :info, body: I18n.t("avo.action_ran_successfully") } - return [default_message] if response[:messages].blank? + return [default_message] if @response[:messages].blank? - response[:messages].select do |message| + @response[:messages].select do |message| # Remove the silent placeholder messages message[:type] != :silent end @@ -114,22 +113,12 @@ def decrypted_arguments ) end - def flash_messages(messages) - messages.each do |message| + def flash_messages + get_messages.each do |message| flash[message[:type]] = message[:body] end end - def keep_modal_open(messages) - flash_messages messages - - respond_to do |format| - format.turbo_stream do - render partial: "avo/partials/flash_alerts" - end - end - end - def verify_authorization raise Avo::NotAuthorizedError.new unless @action.authorized? end diff --git a/app/javascript/js/controllers.js b/app/javascript/js/controllers.js index d4dd260353..0f9cd7e4bc 100644 --- a/app/javascript/js/controllers.js +++ b/app/javascript/js/controllers.js @@ -10,6 +10,7 @@ import CodeFieldController from './controllers/fields/code_field_controller' import CopyToClipboardController from './controllers/copy_to_clipboard_controller' import DashboardCardController from './controllers/dashboard_card_controller' import DateFieldController from './controllers/fields/date_field_controller' +import DownloadController from './controllers/download_controller' import EasyMdeController from './controllers/fields/easy_mde_controller' import FilterController from './controllers/filter_controller' import HiddenInputController from './controllers/hidden_input_controller' @@ -19,7 +20,6 @@ import ItemSelectorController from './controllers/item_selector_controller' import KeyValueController from './controllers/fields/key_value_controller' import LoadingButtonController from './controllers/loading_button_controller' import MenuController from './controllers/menu_controller' -import ModalController from './controllers/modal_controller' import MultipleSelectFilterController from './controllers/multiple_select_filter_controller' import PerPageController from './controllers/per_page_controller' import PreviewController from './controllers/preview_controller' @@ -46,6 +46,7 @@ application.register('attachments', AttachmentsController) application.register('boolean-filter', BooleanFilterController) application.register('copy-to-clipboard', CopyToClipboardController) application.register('dashboard-card', DashboardCardController) +application.register('download', DownloadController) application.register('filter', FilterController) application.register('hidden-input', HiddenInputController) application.register('input-autofocus', InputAutofocusController) @@ -53,7 +54,6 @@ application.register('item-select-all', ItemSelectAllController) application.register('item-selector', ItemSelectorController) application.register('loading-button', LoadingButtonController) application.register('menu', MenuController) -application.register('modal', ModalController) application.register('multiple-select-filter', MultipleSelectFilterController) application.register('per-page', PerPageController) application.register('preview', PreviewController) diff --git a/app/javascript/js/controllers/download_controller.js b/app/javascript/js/controllers/download_controller.js new file mode 100644 index 0000000000..0e05d82386 --- /dev/null +++ b/app/javascript/js/controllers/download_controller.js @@ -0,0 +1,11 @@ +import { Controller } from '@hotwired/stimulus' +import { saveAs } from 'file-saver' + +export default class extends Controller { + connect() { + saveAs( + new Blob([this.element.dataset.content]), + this.element.dataset.filename, + ) + } +} diff --git a/app/javascript/js/controllers/modal_controller.js b/app/javascript/js/controllers/modal_controller.js deleted file mode 100644 index 92eee73051..0000000000 --- a/app/javascript/js/controllers/modal_controller.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Controller } from '@hotwired/stimulus' - -export default class extends Controller { - static targets = ['modal'] - - close() { - this.modalTarget.remove() - - document.dispatchEvent(new Event('actions-modal:close')) - } - - delayedClose() { - const vm = this - - setTimeout(() => { - vm.modalTarget.remove() - document.dispatchEvent(new Event('actions-modal:close')) - }, 500) - } -} diff --git a/app/views/avo/actions/download.turbo_stream.erb b/app/views/avo/actions/download.turbo_stream.erb new file mode 100644 index 0000000000..f7ae1899c0 --- /dev/null +++ b/app/views/avo/actions/download.turbo_stream.erb @@ -0,0 +1,16 @@ +<%# Append a div that triggers the download_controller.js %> +<%= turbo_stream.append "actions_show" do %> + <%= content_tag :div, + data: { + controller: "download", + content: @response[:path], + filename: @response[:filename], + } do %> + <% end %>s +<% end %> + +<%# Remove the actions_show div %> +<%= turbo_stream.remove "actions_show" %> + +<%# Trigger the flash alerts %> +<%= render partial: "avo/partials/flash_alerts" %> diff --git a/app/views/avo/actions/show.html.erb b/app/views/avo/actions/show.html.erb index ac7807fe37..f1a672d666 100644 --- a/app/views/avo/actions/show.html.erb +++ b/app/views/avo/actions/show.html.erb @@ -49,7 +49,7 @@ size: :sm, data: { target: :submit_action, - **@action.class.submit_button_data_attributes + action_target: "submit" } do %> <%= @action.confirm_button_label %> <% end %> diff --git a/lib/avo/base_action.rb b/lib/avo/base_action.rb index 70f362f421..566bd7d438 100644 --- a/lib/avo/base_action.rb +++ b/lib/avo/base_action.rb @@ -10,7 +10,7 @@ class BaseAction class_attribute :no_confirmation, default: false class_attribute :standalone, default: false class_attribute :visible - class_attribute :may_download_file, default: false + class_attribute :may_download_file class_attribute :turbo class_attribute :authorize, default: true @@ -36,23 +36,10 @@ class << self delegate :context, to: ::Avo::Current def form_data_attributes - # We can't respond with a file download from Turbo se we disable it on the form - if may_download_file - {turbo: turbo || false, remote: false} - else - {turbo: turbo, turbo_frame: :_top}.compact - end - end - - # We can't respond with a file download from Turbo se we disable close the modal manually after a while (it's a hack, we know) - def submit_button_data_attributes - attributes = { action_target: "submit" } - - if may_download_file - attributes[:action] = "click->modal#delayedClose" - end - - attributes + { + turbo: turbo, + turbo_frame: :_top + }.compact end def to_param @@ -99,6 +86,10 @@ def initialize(record: nil, resource: nil, user: nil, view: nil, arguments: {}) @response ||= {} @response[:messages] = [] + + if self.may_download_file.present? + puts "[Avo->] WARNING! Since version 3.2.2 'may_download_file' is unecessary and deprecated on actions. Can be safely removed from #{self.class.name}" + end end # Blank method @@ -193,7 +184,7 @@ def warn(text) end def keep_modal_open - response[:keep_modal_open] = true + response[:type] = :keep_modal_open self end diff --git a/package.json b/package.json index 44c589b693..282c0a2a9c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint-config-airbnb": "^19.0.4", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", + "file-saver": "^2.0.5", "flatpickr": "^4.6.13", "heroicons": "^2.0.18", "js-cookie": "^3.0.5", diff --git a/spec/dummy/app/avo/actions/download_file.rb b/spec/dummy/app/avo/actions/download_file.rb index b8fb54782d..e6946ea233 100644 --- a/spec/dummy/app/avo/actions/download_file.rb +++ b/spec/dummy/app/avo/actions/download_file.rb @@ -1,7 +1,6 @@ class Avo::Actions::DownloadFile < Avo::BaseAction self.name = "Download file" self.standalone = true - self.may_download_file = true # TODO: fix fields for actions def fields diff --git a/spec/dummy/app/avo/actions/export_csv.rb b/spec/dummy/app/avo/actions/export_csv.rb index da68b7d14a..2dfed5ab7e 100644 --- a/spec/dummy/app/avo/actions/export_csv.rb +++ b/spec/dummy/app/avo/actions/export_csv.rb @@ -1,7 +1,6 @@ class Avo::Actions::ExportCsv < Avo::BaseAction self.name = "Export csv" self.no_confirmation = false - self.may_download_file = true def handle(**args) records, resource = args.values_at(:records, :resource) diff --git a/yarn.lock b/yarn.lock index ef71d084c7..caccdabbee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2636,6 +2636,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"