Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: profile and cover photos #2722

Merged
merged 15 commits into from
Jul 1, 2024
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), 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:, size:)
@cover_photo = cover_photo
@size = size
end

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

def render?
@cover_photo.present?
end
end
5 changes: 4 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,10 @@ def args
description: @resource.description,
display_breadcrumbs: display_breadcrumbs,
index: 0,
data: {panel_id: "main"}
data: {panel_id: "main"},
cover_photo: @resource.cover_photo_value,
cover_photo_size: @resource.cover_photo_size,
profile_photo: @resource.profile_photo_value
}
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, size: @cover_photo_size %>

<% 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('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
11 changes: 9 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 < ViewComponent::Base

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 < ViewComponent::Base
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, cover_photo_size: :md, **args)
# deprecating title in favor of name
@title = args[:title]
@name = name || title
Expand All @@ -28,6 +28,13 @@ 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
@cover_photo_size = cover_photo_size
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), 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?
end
end
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
26 changes: 26 additions & 0 deletions lib/avo/concerns/has_cover_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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
attr_accessor :cover_photo
end

def cover_photo_size
self.class&.cover_photo&.fetch(:size, :md)
end

def cover_photo_value
return unless self.class&.cover_photo&.fetch(:source).present?

if self.class.cover_photo[:source].is_a?(Symbol)
record.send(self.class.cover_photo[:source])
elsif self.class.cover_photo[:source].respond_to?(:call)
Avo::ExecutionContext.new(target: self.class.cover_photo[:source], record:, resource: self, view:).handle
end
end
end
end
end
22 changes: 22 additions & 0 deletions lib/avo/concerns/has_profile_photo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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
attr_accessor :profile_photo
end

def profile_photo_value
return unless self.class&.profile_photo&.fetch(:source).present?

if self.class.profile_photo[:source].is_a?(Symbol)
record.send(self.class.profile_photo[:source])
elsif self.class.profile_photo[:source].respond_to?(:call)
Avo::ExecutionContext.new(target: self.class.profile_photo[:source], record:, resource: self, view:).handle
end
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
10 changes: 10 additions & 0 deletions spec/dummy/app/avo/resources/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ class Avo::Resources::Event < Avo::BaseResource
self.title = :name
self.description = "An event that happened at a certain time."
self.includes = [:location]
self.cover_photo = {
# size: :sm,
source: :cover_photo
}
self.profile_photo = {
source: :profile_photo
}

def fields
field :name, as: :text, link_to_record: true, sortable: true, stacked: true
field :first_user, as: :record_link
field :event_time, as: :datetime, sortable: true
field :body, as: :trix

field :profile_photo, as: :file, is_image: true
field :cover_photo, as: :file, is_image: true

if params[:show_location_field] == "1"
# Example for error message when resource is missing
field :location, as: :belongs_to
Expand Down
2 changes: 1 addition & 1 deletion spec/dummy/app/avo/resources/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def fields
field :name, as: :text
field :size, as: :text

if params[:show_location_field] == '1'
if params[:show_location_field] == "1"
# Example for error message when resource is missing
field :location, as: :has_one
end
Expand Down
4 changes: 2 additions & 2 deletions spec/dummy/app/avo/resources/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class Avo::Resources::Team < Avo::BaseResource
}

def fields
field :preview, as: :preview

main_panel do
field :preview, as: :preview

unless params[:hide_id]
field :id, as: :id, filterable: true
end
Expand Down
3 changes: 3 additions & 0 deletions spec/dummy/app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Event < ApplicationRecord

belongs_to :location, optional: true

has_one_attached :profile_photo
has_one_attached :cover_photo

def first_user
User.first
end
Expand Down
Binary file added spec/dummy/db/seed_files/dummy-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion spec/features/avo/app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
end

describe "Current.user is set" do
it "displayes the current user" do
it "displays the current user" do
visit "/admin/custom_tool"

# Label on the menu builder
Expand Down
20 changes: 20 additions & 0 deletions spec/features/avo/cover_profile_photos_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "rails_helper"

RSpec.describe "Cover and profile photos", type: :feature do
let!(:event) {
create(:event).tap do |event|
event.profile_photo.attach(io: Avo::Engine.root.join("spec", "dummy", "db", "seed_files", "dummy-image.jpg").open, filename: "dummy-image.jpg")
event.cover_photo.attach(io: Avo::Engine.root.join("spec", "dummy", "db", "seed_files", "dummy-image.jpg").open, filename: "dummy-image.jpg")
end
}

describe "cover photo" do
it "displays the cover photo" do
visit avo.resources_event_path(event)

# Label on the menu
expect(page).to have_selector '[data-resource-name="Avo::Resources::Event"] [data-panel-id="main"] [data-component="avo/cover_photo_component"]'
expect(page).to have_selector '[data-resource-name="Avo::Resources::Event"] [data-panel-id="main"] [data-component="avo/profile_photo_component"]'
end
end
end
7 changes: 7 additions & 0 deletions tailwind.preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ module.exports = {
],
theme: {
extend: {
aspectRatio: {
'cover-sm': '9/2',
'cover-md': '9/3',
'cover-lg': '9/4',
},
colors: {
blue,
gray,
Expand Down Expand Up @@ -152,6 +157,8 @@ module.exports = {
// Add has-sidebar variant to make it easier to target fields in panels and use the full-width
addVariant('has-sidebar', '.has-sidebar & ')
addVariant('has-record-selector', '.has-record-selector & ')
addVariant('has-profile-photo', '.has-profile-photo & ')
addVariant('has-cover-photo', '.has-cover-photo & ')
}),
],
}
Loading