diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bf9a8e7202..1cfe68c16ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -157,8 +157,8 @@ commands: cat /tmp/.gems-versions - restore_cache: keys: - - solidus-installer-v7-{{ checksum "/tmp/.ruby-versions" }}-{{ checksum "/tmp/.gems-versions" }} - - solidus-installer-v7-{{ checksum "/tmp/.ruby-versions" }}- + - solidus-installer-v8-{{ checksum "/tmp/.ruby-versions" }}-{{ checksum "/tmp/.gems-versions" }} + - solidus-installer-v8-{{ checksum "/tmp/.ruby-versions" }}- - run: name: "Prepare the rails application" command: | diff --git a/admin/app/components/solidus_admin/base_component.rb b/admin/app/components/solidus_admin/base_component.rb new file mode 100644 index 00000000000..e54f3c3a8b0 --- /dev/null +++ b/admin/app/components/solidus_admin/base_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "solidus_admin/system/import" + +module SolidusAdmin + # BaseComponent is the base class for all components in Solidus Admin. + class BaseComponent < ViewComponent::Base + include ViewComponent::InlineTemplate + include SolidusAdmin::ContainerHelper + end +end diff --git a/admin/app/components/solidus_admin/main_nav_component.rb b/admin/app/components/solidus_admin/main_nav_component.rb new file mode 100644 index 00000000000..c8e43362d4f --- /dev/null +++ b/admin/app/components/solidus_admin/main_nav_component.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SolidusAdmin + # Renders the main navigation of Solidus Admin. + class MainNavComponent < BaseComponent + include Import[ + "main_nav_item_component", + items: "main_nav_items" + ] + + erb_template <<~ERB + + ERB + + private + + def sorted_items + items.sort_by(&:position) + end + end +end diff --git a/admin/app/components/solidus_admin/main_nav_item_component.rb b/admin/app/components/solidus_admin/main_nav_item_component.rb new file mode 100644 index 00000000000..7f430080213 --- /dev/null +++ b/admin/app/components/solidus_admin/main_nav_item_component.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusAdmin + # Menu item within a {MainNavComponent} + class MainNavItemComponent < BaseComponent + with_collection_parameter :item + + attr_reader :item + + def initialize(item:) + @item = item + super + end + + erb_template <<~ERB + + <%= item.title %> + + ERB + end +end diff --git a/admin/app/controllers/solidus_admin/orders_controller.rb b/admin/app/controllers/solidus_admin/orders_controller.rb index c770fbf00e3..f82940f3b6d 100644 --- a/admin/app/controllers/solidus_admin/orders_controller.rb +++ b/admin/app/controllers/solidus_admin/orders_controller.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module SolidusAdmin + # rubocop:disable Rails/ApplicationController class OrdersController < ActionController::Base layout 'solidus_admin/application' end + # rubocop:enable Rails/ApplicationController end diff --git a/admin/app/helpers/solidus_admin/container_helper.rb b/admin/app/helpers/solidus_admin/container_helper.rb new file mode 100644 index 00000000000..10397eaac79 --- /dev/null +++ b/admin/app/helpers/solidus_admin/container_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "solidus_admin/container" + +module SolidusAdmin + module ContainerHelper + def container + SolidusAdmin::Container + end + + def component(name) + container.resolve("#{name}_component") + end + end +end diff --git a/admin/app/views/layouts/solidus_admin/application.html.erb b/admin/app/views/layouts/solidus_admin/application.html.erb index 4b57101556b..c407f39677a 100644 --- a/admin/app/views/layouts/solidus_admin/application.html.erb +++ b/admin/app/views/layouts/solidus_admin/application.html.erb @@ -5,6 +5,7 @@

Layout is rendered

+ <%= render component("main_nav").new %> <%= yield %> diff --git a/admin/lib/solidus_admin/configuration.rb b/admin/lib/solidus_admin/configuration.rb index d33baeda4ee..ed60b1decdd 100644 --- a/admin/lib/solidus_admin/configuration.rb +++ b/admin/lib/solidus_admin/configuration.rb @@ -12,27 +12,29 @@ class Configuration < Spree::Preferences::Configuration # # You can modify this list to include your own paths: # - # SolidusAdmin::Config.tailwind_content << Rails.root.join("app", "my", "custom", "path") + # SolidusAdmin::Config.tailwind_content << Rails.root.join("app/my/custom/path") # # Recompile with `bin/rails solidus_admin:tailwindcss:build` after changing this list. # # @see https://tailwindcss.com/docs/configuration#content preference :tailwind_content, :array, default: [ - SolidusAdmin::Engine.root.join("public", "*.html"), - SolidusAdmin::Engine.root.join("app", "helpers", "**", "*.rb"), - SolidusAdmin::Engine.root.join("app", "assets", "javascripts", "**", "*.js"), - SolidusAdmin::Engine.root.join("app", "views", "**", "*.{erb,haml,html,slim}"), - Rails.root.join("public", "solidus_admin", "*.html"), - Rails.root.join("app", "helpers", "solidus_admin", "**", "*.rb"), - Rails.root.join("app", "assets", "javascripts", "solidus_admin", "**", "*.js"), - Rails.root.join("app", "views", "solidus_admin", "**", "*.{erb,haml,html,slim}") + SolidusAdmin::Engine.root.join("public/*.html"), + SolidusAdmin::Engine.root.join("app/helpers/**/*.rb"), + SolidusAdmin::Engine.root.join("app/assets/javascripts/**/*.js"), + SolidusAdmin::Engine.root.join("app/views/**/*.{erb,haml,html,slim}"), + SolidusAdmin::Engine.root.join("app/components/**/*.rb"), + Rails.root.join("public/solidus_admin/*.html"), + Rails.root.join("app/helpers/solidus_admin/**/*.rb"), + Rails.root.join("app/assets/javascripts/solidus_admin/**/*.js"), + Rails.root.join("app/views/solidus_admin/**/*.{erb,haml,html,slim}"), + Rails.root.join("app/components/solidus_admin/**/*.rb") ] # List of Tailwind CSS files to be combined into the final stylesheet. # # You can modify this list to include your own files: # - # SolidusAdmin::Config.tailwind_stylesheets << Rails.root.join("app", "assets", "stylesheets", "solidus_admin", "application.tailwind.css") + # SolidusAdmin::Config.tailwind_stylesheets << Rails.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css") # # Recompile with `bin/rails solidus_admin:tailwindcss:build` after changing this list. preference :tailwind_stylesheets, :array, default: [] diff --git a/admin/lib/solidus_admin/container.rb b/admin/lib/solidus_admin/container.rb new file mode 100644 index 00000000000..63fb33122af --- /dev/null +++ b/admin/lib/solidus_admin/container.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "dry/system" +require "dry/system/container" +require "dry/system/component" +require "view_component" +require "solidus_admin/system/loaders/host_overridable_constant" + +module SolidusAdmin + # Global registry for host-injectable components. + # + # We use this container to register all the components that can be + # overridden by the host application. + # + # @api private + class Container < Dry::System::Container + configure do |config| + config.root = Pathname(__FILE__).dirname.join("../..").realpath + config.component_dirs.add("app/components") do |dir| + dir.loader = System::Loaders::HostOverridableConstant.method(:call).curry["components"] + dir.namespaces.add "solidus_admin", key: nil + end + end + + # Returns all the registered components for a given namespace. + # + # @api private + def self.within_namespace(namespace) + keys.filter_map do + _1.start_with?("#{namespace}#{config.namespace_separator}") && resolve(_1) + end + end + end +end diff --git a/admin/lib/solidus_admin/engine.rb b/admin/lib/solidus_admin/engine.rb index e3a31e3ec1e..f7c1545f211 100644 --- a/admin/lib/solidus_admin/engine.rb +++ b/admin/lib/solidus_admin/engine.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require "view_component" +require "solidus_admin/container" + module SolidusAdmin class Engine < ::Rails::Engine isolate_namespace SolidusAdmin @@ -11,5 +14,11 @@ class Engine < ::Rails::Engine initializer "solidus_admin.assets" do |app| app.config.assets.precompile += %w[solidus_admin/application.css] end + + initializer "solidus_admin.main_nav_items_provider" do + require "solidus_admin/system/providers/main_nav" + + Container.start("main_nav") + end end end diff --git a/admin/lib/solidus_admin/main_nav_item.rb b/admin/lib/solidus_admin/main_nav_item.rb new file mode 100644 index 00000000000..b5d5ec88a1f --- /dev/null +++ b/admin/lib/solidus_admin/main_nav_item.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SolidusAdmin + # Encapsulates the data for a main nav item. + class MainNavItem + attr_reader :title, :position + + # @param title [String] + # @param position [Integer] + def initialize(title:, position:) + @title = title + @position = position + end + end +end diff --git a/admin/lib/solidus_admin/system/import.rb b/admin/lib/solidus_admin/system/import.rb new file mode 100644 index 00000000000..340140c2df8 --- /dev/null +++ b/admin/lib/solidus_admin/system/import.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "solidus_admin/container" + +module SolidusAdmin + # Auto-imports container dependencies. + # + # @example + # class Foo + # # Foo.new will have a `#bar` instance method that returns the + # # result of `Container["bar"]`. + # include Import["bar"] + # end + Import = Container.injector +end diff --git a/admin/lib/solidus_admin/system/loaders/host_overridable_constant.rb b/admin/lib/solidus_admin/system/loaders/host_overridable_constant.rb new file mode 100644 index 00000000000..1a711683219 --- /dev/null +++ b/admin/lib/solidus_admin/system/loaders/host_overridable_constant.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module SolidusAdmin + module System + module Loaders + # Loader that resolves constants that can be overriden by the host. + # + # For instance, when the loader is configured like this: + # + # ```ruby + # config.component_dirs.add "app/components" do |dir| + # dir.loader = SolidusAdmin::System::Loaders::HostOverridableConstant.method(:call).curry["components"] + # end + # ``` + # + # When `Container["foo"]` is given and the loader is used: + # + # - It will return a `MyApp::SolidusAdmin::Foo` constant if `app/components/my_app/solidus_admin/foo.rb` exists. + # - Otherwise, it will return the resolved constant from the engine. + # + # @api private + class HostOverridableConstant < Dry::System::Loader + # @param [String] namespace + def self.call(namespace, component, *_args) + return override_constant(component) if override_path(namespace, component).exist? + + require!(component) + constant(component) + end + + class << self + private + + def application + Rails.application + end + + def application_name + Rails.application.class.module_parent.name + end + + def override_path(namespace, component) + application + .root + .join( + "app", + namespace, + application_name.underscore, + "solidus_admin", + "#{component.identifier.key}.rb" + ) + end + + def override_constant(component) + "#{application_name}::SolidusAdmin::#{component.identifier.key.camelize}".constantize + end + end + end + end + end +end diff --git a/admin/lib/solidus_admin/system/providers/main_nav.rb b/admin/lib/solidus_admin/system/providers/main_nav.rb new file mode 100644 index 00000000000..38c94f84799 --- /dev/null +++ b/admin/lib/solidus_admin/system/providers/main_nav.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "solidus_admin/container" +require "solidus_admin/main_nav_item" + +module SolidusAdmin + module Providers + Container.register_provider("main_nav") do + start do + container.namespace("main_nav") do + register("first_item", MainNavItem.new(title: "First item", position: "10")) + register("second_item", MainNavItem.new(title: "Second item", position: "20")) + register("third_item", MainNavItem.new(title: "Third item", position: "30")) + end + + container.register("main_nav_items") do + Container.within_namespace("main_nav") + end + end + end + end +end diff --git a/admin/lib/solidus_admin/tailwindcss.rb b/admin/lib/solidus_admin/tailwindcss.rb index 5d02564425d..c94f8319f40 100644 --- a/admin/lib/solidus_admin/tailwindcss.rb +++ b/admin/lib/solidus_admin/tailwindcss.rb @@ -17,9 +17,9 @@ def run(args = "") "application.tailwind.css" ) - system "#{::Tailwindcss::Engine.root.join("exe/tailwindcss")} \ + system "#{::Tailwindcss::Engine.root.join('exe/tailwindcss')} \ -i #{stylesheet_file.path} \ - -o #{Rails.root.join("app/assets/builds/solidus_admin/tailwind.css")} \ + -o #{Rails.root.join('app/assets/builds/solidus_admin/tailwind.css')} \ -c #{config_file.path} \ #{args}" ensure @@ -28,25 +28,25 @@ def run(args = "") end def config_app_path - Rails.root.join("config", "solidus_admin", "tailwind.config.js.erb") + Rails.root.join("config/solidus_admin/tailwind.config.js.erb") end def config_engine_path - SolidusAdmin::Engine.root.join("config", "solidus_admin", "tailwind.config.js.erb") + SolidusAdmin::Engine.root.join("config/solidus_admin/tailwind.config.js.erb") end def stylesheet_app_path - Rails.root.join("app", "assets", "stylesheets", "solidus_admin", "application.tailwind.css.erb") + Rails.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css.erb") end def stylesheet_engine_path - SolidusAdmin::Engine.root.join("app", "assets", "stylesheets", "solidus_admin", "application.tailwind.css.erb") + SolidusAdmin::Engine.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css.erb") end def compile_to_tempfile(path, name) Tempfile.new(name).tap do |file| path - .then { |path| File.read(path) } + .then { File.read(_1) } .then { |content| ERB.new(content) } .then { |erb| erb.result } .then { |compiled_content| file.write(compiled_content) && file.rewind } diff --git a/admin/lib/tasks/tailwindcss.rake b/admin/lib/tasks/tailwindcss.rake index f72bf28398c..2b19ad2a95a 100644 --- a/admin/lib/tasks/tailwindcss.rake +++ b/admin/lib/tasks/tailwindcss.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + namespace :solidus_admin do namespace :tailwindcss do require "solidus_admin/tailwindcss" diff --git a/admin/solidus_admin.gemspec b/admin/solidus_admin.gemspec index 86b3e62be55..389d6cf60d2 100644 --- a/admin/solidus_admin.gemspec +++ b/admin/solidus_admin.gemspec @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative '../core/lib/spree/core/version.rb' +require_relative '../core/lib/spree/core/version' Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -23,6 +23,8 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 3.0.0' s.required_rubygems_version = '>= 1.8.23' + s.add_dependency 'dry-system', '~> 1.0' s.add_dependency 'solidus_core', s.version s.add_dependency 'tailwindcss-rails', '~> 2.0' + s.add_dependency 'view_component', '~> 3.0' end diff --git a/solidus.gemspec b/solidus.gemspec index f6fb626e277..f46b96100a3 100644 --- a/solidus.gemspec +++ b/solidus.gemspec @@ -21,9 +21,9 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 3.0.0' s.required_rubygems_version = '>= 1.8.23' + s.add_dependency 'solidus_admin', s.version s.add_dependency 'solidus_api', s.version s.add_dependency 'solidus_backend', s.version s.add_dependency 'solidus_core', s.version s.add_dependency 'solidus_sample', s.version - s.add_dependency 'solidus_admin', s.version end