lib/active_admin/menu.rb



# frozen_string_literal: true
module ActiveAdmin

  # Each Namespace builds up it's own menu as the global navigation
  #
  # To build a new menu:
  #
  #   menu = Menu.new do |m|
  #     m.add label: 'Dashboard', url: '/'
  #     m.add label: 'Users',     url: '/users'
  #   end
  #
  # If you're interested in configuring a menu item, take a look at the
  # options available in `ActiveAdmin::MenuItem`
  #
  class Menu

    def initialize
      super # MenuNode
      yield(self) if block_given?
    end

    module MenuNode
      def initialize
        @children = {}
      end

      def [](id)
        @children[normalize_id(id)]
      end

      def []=(id, child)
        @children[normalize_id(id)] = child
      end

      # Recursively builds any given menu items. There are two syntaxes supported,
      # as shown in the below examples. Both create an identical menu structure.
      #
      # Example 1:
      #   menu = Menu.new
      #   menu.add label: 'Dashboard' do |dash|
      #     dash.add label: 'My Child Dashboard'
      #   end
      #
      # Example 2:
      #   menu = Menu.new
      #   menu.add label:  'Dashboard'
      #   menu.add parent: 'Dashboard', label: 'My Child Dashboard'
      #
      def add(options)
        options = options.dup # Make sure parameter is not modified
        item = if parent = options.delete(:parent)
                 (self[parent] || add(label: parent)).add options
               else
                 _add options.merge parent: self
               end

        yield(item) if block_given?

        item
      end

      # Whether any children match the given item.
      def include?(item)
        @children.value?(item)
      end

      # Used in the UI to visually distinguish which menu item is selected.
      def current?(item)
        self == item || include?(item)
      end

      # Returns sorted array of menu items that should be displayed in this context.
      # Sorts by priority first, then alphabetically by label if needed.
      def items(context = nil)
        @children.values.select { |i| i.display?(context) }.sort do |a, b|
          result = a.priority <=> b.priority
          result = a.label(context) <=> b.label(context) if result == 0
          result
        end
      end

      attr_reader :children
      private
      attr_writer :children

      # The method that actually adds new menu items. Called by the public method.
      # If this ID is already taken, transfer the children of the existing item to the new item.
      def _add(options)
        item = ActiveAdmin::MenuItem.new(options)
        item.send :children=, self[item.id].children if self[item.id]
        self[item.id] = item
      end

      def normalize_id(id)
        case id
        when String, Symbol, ActiveModel::Name
          id.to_s.downcase.tr " ", "_"
        when ActiveAdmin::Resource::Name
          id.param_key
        else
          raise TypeError, "#{id.class} isn't supported as a Menu ID"
        end
      end
    end

    include MenuNode

  end
end