From bd088a1164672990c946da454738628942aa73bb Mon Sep 17 00:00:00 2001 From: Svyatoslav Kryukov Date: Mon, 28 Oct 2024 11:49:33 +0300 Subject: [PATCH] Implement inertia_ui_modal_renderer --- CHANGELOG.md | 4 ++ README.md | 32 +++++++++++++ lib/inertia_rails_contrib.rb | 15 +++++- lib/inertia_rails_contrib/configuration.rb | 10 ++++ lib/inertia_rails_contrib/engine.rb | 9 ++++ lib/inertia_rails_contrib/inertia_ui_modal.rb | 27 +++++++++++ .../inertia_ui_modal/inertia_rails_patch.rb | 20 ++++++++ .../inertia_ui_modal/redirect.rb | 16 +++++++ .../inertia_ui_modal/renderer.rb | 48 +++++++++++++++++++ 9 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 lib/inertia_rails_contrib/configuration.rb create mode 100644 lib/inertia_rails_contrib/engine.rb create mode 100644 lib/inertia_rails_contrib/inertia_ui_modal.rb create mode 100644 lib/inertia_rails_contrib/inertia_ui_modal/inertia_rails_patch.rb create mode 100644 lib/inertia_rails_contrib/inertia_ui_modal/redirect.rb create mode 100644 lib/inertia_rails_contrib/inertia_ui_modal/renderer.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb434d..d6ba2d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] +Added: + +- [InertiaUI Modal](https://github.com/inertiaui/modal) support ([@skryukov]) + ## [0.3.0] - 2024-10-25 Added: diff --git a/README.md b/README.md index 3a7e1cd..5d933fd 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,38 @@ $ bin/rails generate inertia:scaffold Post title:string body:text - `inertia_templates` - default - `inertia_tw_templates` - Tailwind CSS +## InertiaUI Modal Support + +`InertiaRailsContrib` provides support for [InertiaUI Modal](https://github.com/inertiaui/modal) in the Rails application. + +With InertiaUI Modal, you can easily open any route in a Modal or Slideover without having to change anything about your existing routes or controllers. + +By default, InertiaUI Modal doesn't require anything from the Inertia Server Adapters, since it just opens modals without changing the URL. +However, InertiaUI Modal also supports updating the URL when opening a modal (see the docs on why you might want that: https://inertiaui.com/inertia-modal/docs/base-route-url). + +### Setup + +1. Follow the NPM installation instructions from the [InertiaUI Modal documentation](https://inertiaui.com/inertia-modal/docs/installation). + +2. Enable InertiaUI Modal, turn on the `enable_inertia_ui_modal` option in the `inertia_rails_contrib.rb` initializer: + +```ruby +InertiaRailsContrib.configure do |config| + config.enable_inertia_ui_modal = true +end +``` + +This will add a new render method `inertia_modal` that can be used in the controller actions: + +```ruby +class PostsController < ApplicationController + def new + render inertia_modal: 'Post/New', props: { post: Post.new }, base_url: posts_path + end +end +``` + + ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. diff --git a/lib/inertia_rails_contrib.rb b/lib/inertia_rails_contrib.rb index 4e79ed7..6002414 100644 --- a/lib/inertia_rails_contrib.rb +++ b/lib/inertia_rails_contrib.rb @@ -1,8 +1,21 @@ # frozen_string_literal: true -require "inertia_rails" if defined?(Rails) +if defined?(Rails) + require "inertia_rails" + require_relative "inertia_rails_contrib/engine" +end require_relative "inertia_rails_contrib/version" +require_relative "inertia_rails_contrib/configuration" module InertiaRailsContrib + class << self + def configuration + @configuration ||= Configuration.new + end + + def configure + yield(configuration) + end + end end diff --git a/lib/inertia_rails_contrib/configuration.rb b/lib/inertia_rails_contrib/configuration.rb new file mode 100644 index 0000000..03b7253 --- /dev/null +++ b/lib/inertia_rails_contrib/configuration.rb @@ -0,0 +1,10 @@ +# lib/inertia_rails_contrib/engine.rb +module InertiaRailsContrib + class Configuration + attr_accessor :enable_inertia_ui_modal + + def initialize + @enable_inertia_ui_modal = false + end + end +end diff --git a/lib/inertia_rails_contrib/engine.rb b/lib/inertia_rails_contrib/engine.rb new file mode 100644 index 0000000..26353d8 --- /dev/null +++ b/lib/inertia_rails_contrib/engine.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module InertiaRailsContrib + class Engine < ::Rails::Engine + initializer "inertia_rails_contrib.inertia_ui_modal" do + require_relative "inertia_ui_modal" if InertiaRailsContrib.configuration.enable_inertia_ui_modal + end + end +end diff --git a/lib/inertia_rails_contrib/inertia_ui_modal.rb b/lib/inertia_rails_contrib/inertia_ui_modal.rb new file mode 100644 index 0000000..d64206e --- /dev/null +++ b/lib/inertia_rails_contrib/inertia_ui_modal.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "inertia_ui_modal/renderer" +require_relative "inertia_ui_modal/redirect" +require_relative "inertia_ui_modal/inertia_rails_patch" + +module InertiaRailsContrib + module InertiaUIModal + HEADER_BASE_URL = "X-InertiaUI-Modal-Base-Url" + HEADER_USE_ROUTER = "X-InertiaUI-Modal-Use-Router" + end +end + +ActionController::Renderers.add :inertia_modal do |component, options| + InertiaRailsContrib::InertiaUIModal::Renderer.new( + component, + self, + request, + response, + method(:render), + **options + ).render +end + +ActiveSupport.on_load(:action_controller_base) do + prepend ::InertiaRailsContrib::InertiaUIModal::Redirect +end diff --git a/lib/inertia_rails_contrib/inertia_ui_modal/inertia_rails_patch.rb b/lib/inertia_rails_contrib/inertia_ui_modal/inertia_rails_patch.rb new file mode 100644 index 0000000..5fd2fdf --- /dev/null +++ b/lib/inertia_rails_contrib/inertia_ui_modal/inertia_rails_patch.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "inertia_rails/renderer" + +module InertiaRailsContrib + module InertiaUIModal + module RendererPatch + def page + super.tap do |modal| + if @request.env[:_inertiaui_modal] + modal[:props][:_inertiaui_modal] = @request.env[:_inertiaui_modal] + modal[:url] = modal[:props][:_inertiaui_modal][:url] + end + end + end + end + end +end + +InertiaRails::Renderer.prepend(InertiaRailsContrib::InertiaUIModal::RendererPatch) diff --git a/lib/inertia_rails_contrib/inertia_ui_modal/redirect.rb b/lib/inertia_rails_contrib/inertia_ui_modal/redirect.rb new file mode 100644 index 0000000..fda1baa --- /dev/null +++ b/lib/inertia_rails_contrib/inertia_ui_modal/redirect.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module InertiaRailsContrib + module InertiaUIModal + module Redirect + def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options) + inertia_modal_referer = request.headers[HEADER_BASE_URL] + if inertia_modal_referer && (allow_other_host || _url_host_allowed?(inertia_modal_referer)) + redirect_to inertia_modal_referer, allow_other_host: allow_other_host, **options + else + super + end + end + end + end +end diff --git a/lib/inertia_rails_contrib/inertia_ui_modal/renderer.rb b/lib/inertia_rails_contrib/inertia_ui_modal/renderer.rb new file mode 100644 index 0000000..a3386a5 --- /dev/null +++ b/lib/inertia_rails_contrib/inertia_ui_modal/renderer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module InertiaRailsContrib + module InertiaUIModal + class Renderer + def initialize(component, controller, request, response, render_method, base_url: nil, **options) + @request = request + @response = response + @base_url = base_url + @inertia_renderer = InertiaRails::Renderer.new(component, controller, request, response, render_method, **options) + end + + def render + if @request.headers[HEADER_USE_ROUTER] == "0" || base_url.blank? + return @inertia_renderer.render + end + + @request.env[:_inertiaui_modal] = @inertia_renderer.page.merge(baseUrl: base_url) + + render_base_url + end + + def base_url + @request.headers[HEADER_BASE_URL] || @base_url + end + + def render_base_url + original_env = Rack::MockRequest.env_for( + base_url, + method: @request.method, + params: @request.params + ) + @request.each_header do |k, v| + original_env[k] ||= v + end + + original_request = ActionDispatch::Request.new(original_env) + + path = ActionDispatch::Journey::Router::Utils.normalize_path(original_request.path_info) + Rails.application.routes.recognize_path_with_request(original_request, path, {}) + controller = original_request.controller_class.new + controller.request = @request + controller.response = @response + controller.process(original_request.path_parameters[:action]) + end + end + end +end