Skip to content

Commit

Permalink
feature: profile and cover photos (#2722)
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Bob <paul.ionut.bob@gmail.com>
  • Loading branch information
adrianthedev and Paul-Bob authored Jul 1, 2024
1 parent 439292f commit 99bfd50
Show file tree
Hide file tree
Showing 28 changed files with 358 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .erb-lint_rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ Lint/UselessAssignment:

Layout/FirstArgumentIndentation:
Enabled: false

Layout/ArgumentAlignment:
Enabled: false
4 changes: 4 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
contact_links:
- name: Priority Support
url: https://avohq.io/support
about: Get fast support from the authors of Avo
3 changes: 3 additions & 0 deletions app/components/avo/cover_photo_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="related w-full -mt-4 md:-mt-4 lg:-mt-6 mb-2 flex flex-col">
<%= image_tag helpers.main_app.url_for(@cover_photo.value), class: class_names("w-full object-cover", size_class), data: {component: self.class.to_s.underscore} %>
</div>
19 changes: 19 additions & 0 deletions app/components/avo/cover_photo_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

class Avo::CoverPhotoComponent < ViewComponent::Base
def initialize(cover_photo:)
@cover_photo = cover_photo
@size = cover_photo&.size
end

# aspect-cover-sm
# aspect-cover-md
# aspect-cover-lg
def size_class
"aspect-cover-#{@size}"
end

def render?
@cover_photo.present? && @cover_photo.visible_in_current_view?
end
end
4 changes: 3 additions & 1 deletion app/components/avo/items/panel_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def args
description: @resource.description,
display_breadcrumbs: display_breadcrumbs,
index: 0,
data: {panel_id: "main"}
data: {panel_id: "main"},
cover_photo: @resource.cover_photo,
profile_photo: @resource.profile_photo
}
else
{name: @item.name, description: @item.description, index: @index}
Expand Down
49 changes: 28 additions & 21 deletions app/components/avo/panel_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
<%= content_tag :div, data: data_attributes, class: classes do %>
<%= render Avo::CoverPhotoComponent.new cover_photo: @cover_photo %>
<% if render_header? %>
<div data-target="panel-header" class="mb-4">
<% if display_breadcrumbs? %>
<div class="breadcrumbs mb-2">
<%= helpers.render_avo_breadcrumbs(separator: helpers.svg("heroicons/outline/chevron-right", class: 'inline-block h-3 stroke-current relative top-[-1px] ml-1' )) if Avo.configuration.display_breadcrumbs %>
</div>
<% end %>
<div class="flex-1 flex flex-col xl:flex-row justify-between gap-1">
<div class="overflow-hidden flex flex-col">
<% if name_slot? %>
<%= name_slot %>
<% else %>
<%= render Avo::PanelNameComponent.new name: @name %>
<% end %>
<% if description.present? %>
<div class="text-sm tracking-normal font-medium text-gray-600" data-target="description">
<%== description %>
<div data-target="panel-header" class="flex flex-col flex-1 w-full mb-4">
<div class="flex justify-center sm:justify-start flex-col sm:flex-row w-full flex-1 has-cover-photo:mt-0">
<%= render Avo::ProfilePhotoComponent.new profile_photo: @profile_photo %>
<div class="flex flex-col flex-1 w-full">
<% if display_breadcrumbs? %>
<div class="breadcrumbs text-center sm:text-left mb-2">
<%= helpers.render_avo_breadcrumbs(separator: helpers.svg("chevron-right", class: "inline-block h-3 stroke-current relative top-[-1px] ml-1")) if Avo.configuration.display_breadcrumbs %>
</div>
<% end %>
</div>
<% if tools.present? %>
<div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end gap-2 mt-4 xl:mt-0" data-target="panel-tools">
<%= tools %>
<div class="flex-1 flex flex-col xl:flex-row justify-between gap-1 grow-0">
<div class="overflow-hidden flex flex-col">
<% if name_slot? %>
<%= name_slot %>
<% else %>
<%= render Avo::PanelNameComponent.new name: @name %>
<% end %>
<% if description.present? %>
<div class="text-sm tracking-normal font-medium text-gray-600 text-center sm:text-left" data-target="description">
<%== description %>
</div>
<% end %>
</div>
<% if tools.present? %>
<div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end gap-2 mt-4 xl:mt-0" data-target="panel-tools">
<%= tools %>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
Expand Down
10 changes: 8 additions & 2 deletions app/components/avo/panel_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ class Avo::PanelComponent < Avo::BaseComponent

attr_reader :title # deprecating title in favor of name
attr_reader :name
attr_reader :classes

delegate :white_panel_classes, to: :helpers

renders_one :cover_slot
renders_one :name_slot
renders_one :tools
renders_one :body
Expand All @@ -18,7 +18,7 @@ class Avo::PanelComponent < Avo::BaseComponent
renders_one :footer_tools
renders_one :footer

def initialize(name: nil, description: nil, body_classes: nil, data: {}, display_breadcrumbs: false, index: nil, classes: nil, **args)
def initialize(name: nil, description: nil, body_classes: nil, data: {}, display_breadcrumbs: false, index: nil, classes: nil, profile_photo: nil, cover_photo: nil, **args)
# deprecating title in favor of name
@title = args[:title]
@name = name || title
Expand All @@ -28,6 +28,12 @@ def initialize(name: nil, description: nil, body_classes: nil, data: {}, display
@data = data
@display_breadcrumbs = display_breadcrumbs
@index = index
@profile_photo = profile_photo
@cover_photo = cover_photo
end

def classes
class_names(@classes, "has-cover-photo": @cover_photo.present?, "has-profile-photo": @profile_photo.present?)
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/panel_name_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="text-2xl tracking-normal font-semibold text-gray-800 items-center flex flex-1" data-target="title">
<span><%= link_to_if @url.present?, @name, @url, target: @target, class: class_names("text-gray-800", @classes) %></span>
<span class="block w-full text-center sm:text-left"><%= link_to_if @url.present?, @name, @url, target: @target, class: class_names("text-gray-800", @classes) %></span>
<%= body %>
</div>
6 changes: 6 additions & 0 deletions app/components/avo/profile_photo_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= image_tag helpers.main_app.url_for(@profile_photo.value), class: class_names(
"relative rounded-full object-cover aspect-square border-4 border-application",
"has-cover-photo:sm:ml-8 sm:mr-2",
"self-center sm:self-start",
"size-24 has-cover-photo:size-36 has-cover-photo:sm:size-48 has-cover-photo:-mt-12",
), data: {component: self.class.to_s.underscore} %>
11 changes: 11 additions & 0 deletions app/components/avo/profile_photo_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class Avo::ProfilePhotoComponent < ViewComponent::Base
def initialize(profile_photo:)
@profile_photo = profile_photo
end

def render?
@profile_photo.present? && @profile_photo.visible_in_current_view?
end
end
19 changes: 12 additions & 7 deletions app/components/avo/views/resource_index_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
**@resource.stimulus_data_attributes
} do %>
<%= render_cards_component %>
<%= render Avo::PanelComponent.new(description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
<%= render Avo::PanelComponent.new(
description: description,
cover_photo: resource.cover_photo,
data: {component: "resources-index"},
display_breadcrumbs: @reflection.blank?
) do |c| %>
<% c.with_name_slot do %>
<%= render Avo::PanelNameComponent.new name: title, url: params[:turbo_frame].present? && linkable? ? field.frame_url(add_turbo_frame: false) : nil, target: :_blank do |panel_name_component| %>
<%= render Avo::PanelNameComponent.new name: title, url: (params[:turbo_frame].present? && linkable?) ? field.frame_url(add_turbo_frame: false) : nil, target: :_blank do |panel_name_component| %>
<% panel_name_component.with_body do %>
<% if reloadable %>
<%= button_tag data: { controller: "panel-refresh", action: "click->panel-refresh#refresh" } do %>
Expand All @@ -28,10 +33,10 @@
<div class="flex flex-col">
<%= render scopes_list if can_render_scopes? %>
<div class="flex flex-col">
<div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' unless header_visible? %>">
<div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= "hidden" unless header_visible? %>">
<div class="flex items-center px-4 w-64">
<% if show_search_input %>
<%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
<%= render partial: "avo/partials/resource_search", locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
<% else %>
<%# Offset for the space-y-2 property when the search is missing %>
<div class="-mb-2"></div>
Expand All @@ -43,7 +48,7 @@
<%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters, parent_record: parent_record %>
<%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: turbo_frame } %>
<%= render partial: "avo/partials/view_toggle_button", locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: turbo_frame } %>
</div>
</div>
<% if has_dynamic_filters? %>
Expand Down Expand Up @@ -74,7 +79,7 @@
<% if view_type.to_sym == :table || view_type.to_sym == :map %>
<% if @records.present? %>
<div class="mt-4">
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_record: parent_record, discreet_pagination: field&.discreet_pagination %>
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: turbo_frame || "none", index_params: @index_params, resource: @resource, parent_record: parent_record, discreet_pagination: field&.discreet_pagination %>
</div>
<% end %>
<% end %>
Expand All @@ -83,7 +88,7 @@
<%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: parent_record, parent_resource: parent_resource, actions: actions) %>
</div>
<div class="mt-6">
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_record: parent_record, discreet_pagination: field&.discreet_pagination %>
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: turbo_frame || "none", index_params: @index_params, resource: @resource, parent_record: parent_record, discreet_pagination: field&.discreet_pagination %>
</div>
<% end %>
<% end %>
Expand Down
2 changes: 2 additions & 0 deletions lib/avo/base_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class BaseResource
include Avo::Concerns::HasResourceStimulusControllers
include Avo::Concerns::ModelClassConstantized
include Avo::Concerns::HasDescription
include Avo::Concerns::HasCoverPhoto
include Avo::Concerns::HasProfilePhoto
include Avo::Concerns::HasHelpers
include Avo::Concerns::Hydration
include Avo::Concerns::Pagination
Expand Down
18 changes: 18 additions & 0 deletions lib/avo/concerns/has_cover_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Adds the ability to set the visibility of an item in the execution context.
module Avo
module Concerns
module HasCoverPhoto
extend ActiveSupport::Concern

class_methods do
# Add class property to capture the settings
attr_accessor :cover_photo
end

# Add instance property to compute the options
def cover_photo
Avo::CoverPhoto.new resource: self
end
end
end
end
18 changes: 18 additions & 0 deletions lib/avo/concerns/has_profile_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Adds the ability to set the visibility of an item in the execution context.
module Avo
module Concerns
module HasProfilePhoto
extend ActiveSupport::Concern

class_methods do
# Add class property to capture the settings
attr_accessor :profile_photo
end

# Add instance property to compute the options
def profile_photo
ProfilePhoto.new resource: self
end
end
end
end
2 changes: 1 addition & 1 deletion lib/avo/configuration/branding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def initialize(colors: nil, chart_colors: nil, logo: nil, logomark: nil, placeho
@favicon = favicon

@default_colors = {
background: "#F6F6F7",
:background => "#F6F6F7",
100 => "206 231 248",
400 => "57 158 229",
500 => "8 134 222",
Expand Down
11 changes: 11 additions & 0 deletions lib/avo/cover_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Avo
class CoverPhoto < PhotoObject
def key = :cover_photo

def size
options.fetch(:size, :md)
end
end
end
56 changes: 56 additions & 0 deletions lib/avo/photo_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

class Avo::PhotoObject
def initialize(resource:)
@resource = resource
end

delegate :record, to: :@resource
delegate :view, to: :@resource

def options
@options ||= if @resource.class&.send(key).present?
@resource.class&.send(key)
else
{}
end
end

def value
return unless options.fetch(:source, nil).present?

if options[:source].is_a?(Symbol)
record.send(options[:source])
elsif options[:source].respond_to?(:call)
Avo::ExecutionContext.new(target: options[:source], record:, resource: @resource, view:).handle
end
end

def visible_on
@visible_on ||= Array.wrap(options[:visible_on] || [:show, :forms])
end

def visible_in_current_view?
send(:"visible_on_#{view}?")
end

def present?
value.present?
end

def visible_on_index? = visible_in_either?(:index, :display)

def visible_on_show? = visible_in_either?(:show, :display)

def visible_on_edit? = visible_in_either?(:edit, :forms)

def visible_on_new? = visible_in_either?(:new, :forms)

private

def visible_in_either?(*options)
options.map do |option|
visible_on.include?(option)
end.uniq.first.eql?(true)
end
end
7 changes: 7 additions & 0 deletions lib/avo/profile_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Avo
class ProfilePhoto < PhotoObject
def key = :profile_photo
end
end
2 changes: 1 addition & 1 deletion lib/avo/view_inquirer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def ==(other)
end

def in?(another_object)
super another_object.map(&:to_s)
super(another_object.map(&:to_s))
end
end
end
Loading

0 comments on commit 99bfd50

Please sign in to comment.