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: discreet information #3592

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/components/avo/discreet_information_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="flex gap-2 ml-2 mt-1">
<% items.each do |item| %>
<%= content_tag element_tag(item), **element_attributes(item), class: "flex gap-1 text-xs font-normal text-gray-600 hover:text-gray-900", title: item.tooltip, data: {tippy: :tooltip} do %>
<%= item.label if item.label.present? %> <%= helpers.svg item.icon, class: "text-2xl h-4" %>
<% end %>
<% end %>
</div>
25 changes: 25 additions & 0 deletions app/components/avo/discreet_information_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

class Avo::DiscreetInformationComponent < Avo::BaseComponent
prop :payload

def items
@payload.items.compact
end

def element_tag(item)
if item.url.present?
:a
else
:div
end
end

def element_attributes(item)
if item.url.present?
{href: item.url, target: item.url_target}
else
{}
end
end
end
3 changes: 2 additions & 1 deletion app/components/avo/items/panel_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def args
data: {panel_id: "main"},
cover_photo: @resource.cover_photo,
profile_photo: @resource.profile_photo,
external_link: @resource.get_external_link
external_link: @resource.get_external_link,
discreet_information: @resource.discreet_information
}
else
{name: @item.name, description: @item.description, index: @index}
Expand Down
3 changes: 2 additions & 1 deletion app/components/avo/panel_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
description: @description,
display_breadcrumbs: @display_breadcrumbs,
profile_photo: @profile_photo,
external_link: @external_link
external_link: @external_link,
discreet_information: @discreet_information
) do |header| %>
<% if name_slot.present? %>
<% header.with_name_slot do %>
Expand Down
3 changes: 2 additions & 1 deletion app/components/avo/panel_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ class Avo::PanelComponent < Avo::BaseComponent
prop :body_classes
prop :data, default: {}.freeze
prop :display_breadcrumbs, default: false
prop :discreet_information
prop :index
prop :classes
prop :profile_photo
prop :cover_photo
prop :args, kind: :**, default: {}.freeze
prop :external_link

def after_initialize
@name = @args.dig(:name) || @args.dig(:title)
end
prop :external_link

def classes
class_names(@classes, "has-cover-photo": @cover_photo.present?, "has-profile-photo": @profile_photo.present?)
Expand Down
3 changes: 3 additions & 0 deletions app/components/avo/panel_header_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<%= svg "heroicons/outline/arrow-top-right-on-square", class: "ml-2 text-2xl h-4" %>
<% end %>
<% end %>
<% if @discreet_information.present? %>
<%= render Avo::DiscreetInformationComponent.new(payload: @discreet_information) %>
<% end %>
<% end %>
<% end %>
<% end %>
Expand Down
1 change: 1 addition & 0 deletions app/components/avo/panel_header_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Avo::PanelHeaderComponent < Avo::BaseComponent
prop :external_link
prop :description
prop :display_breadcrumbs, default: false
prop :discreet_information
prop :profile_photo

private
Expand Down
1 change: 1 addition & 0 deletions app/javascript/avo.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ document.addEventListener('keyup', (event) => {
function initTippy() {
tippy('[data-tippy="tooltip"]', {
theme: 'light',
allowHTML: true,
content(reference) {
const title = reference.getAttribute('title')
reference.removeAttribute('title')
Expand Down
15 changes: 15 additions & 0 deletions lib/avo/concerns/has_discreet_information.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Avo
module Concerns
module HasDiscreetInformation
extend ActiveSupport::Concern

included do
class_attribute :discreet_information, instance_accessor: false
end

def discreet_information
::Avo::DiscreetInformation.new resource: self
end
end
end
end
44 changes: 44 additions & 0 deletions lib/avo/discreet_information.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class Avo::DiscreetInformation
extend PropInitializer::Properties

prop :resource, reader: :public

delegate :record, :view, to: :resource

def items
Array.wrap(resource.class.discreet_information).map do |item|
if item == :timestamps
timestamp_item(item)
else
parse_payload(item)
end
end
end

private

def timestamp_item(item)
time_format = "%Y-%m-%d %H:%M:%S"
created_at = record.created_at.strftime(time_format)
updated_at = record.updated_at.strftime(time_format)

DiscreetInformationItem.new(
tooltip: I18n.t('avo.discreet_information_timestamps_html', created_at:, updated_at:),
adrianthedev marked this conversation as resolved.
Show resolved Hide resolved
icon: "heroicons/outline/clock"
)
end

def parse_payload(item)
return unless item.is_a?(Hash)

DiscreetInformationItem.new(
tooltip: Avo::ExecutionContext.new(target: item[:tooltip], record: record, resource: self, view: view).handle,
icon: Avo::ExecutionContext.new(target: item[:icon], record: record, resource: self, view: view).handle,
url: Avo::ExecutionContext.new(target: item[:url], record: record, resource: self, view: view).handle,
url_target: Avo::ExecutionContext.new(target: item[:url_target], record: record, resource: self, view: view).handle,
label: Avo::ExecutionContext.new(target: item[:label], record: record, resource: self, view: view).handle,
adrianthedev marked this conversation as resolved.
Show resolved Hide resolved
)
end

DiscreetInformationItem = Struct.new(:tooltip, :icon, :url, :url_target, :label, keyword_init: true) unless defined?(DiscreetInformationItem)
end
30 changes: 29 additions & 1 deletion lib/avo/execution_context.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module Avo
# = Avo Execution Context
#
# The ExecutionContext class is used to evaluate blocks in isolation.
class ExecutionContext
include Avo::Concerns::HasHelpers

Expand Down Expand Up @@ -36,7 +39,32 @@ def initialize(**args)
delegate :result, to: :card
delegate :authorize, to: Avo::Services::AuthorizationService

# Return target if target is not callable, otherwise, execute target on this instance context
# Executes the target and returns the result.
# It takes in a target which usually is a block. If it's something else, it will return it.
#
# It automatically has access to the view context, current user, request, main app, avo, locale, and params.
# It also has a +delegate_missing_to+ which allows it to delegate missing methods to the view context for a more natural experience.
# You may pass extra arguments to the initialize method to have them available in the block that will be executed.
# You may pass extra modules to extend the class with.
#
# ==== Examples
#
# ===== Normal use
#
# Avo::ExecutionContext.new(target: -> { "Hello, world!" }).handle
# => "Hello, world!"
#
# ===== Providing a record
#
# Avo::ExecutionContext.new(target: -> { record.name }, record: @record).handle
# => "John Doe"
#
# ===== Providing a module
#
# This will include the SanitizeHelper module in the class and so have the +sanitize+ method available.
#
# Avo::ExecutionContext.new(target: -> { sanitize "<script>alert('be careful');</script>#{record.name}" } record: @record, include: [ActionView::Helpers::SanitizeHelper]).handle
# => "John Doe"
def handle
target.respond_to?(:call) ? instance_exec(&target) : target
end
Expand Down
1 change: 1 addition & 0 deletions lib/avo/resources/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Base
include Avo::Concerns::Hydration
include Avo::Concerns::Pagination
include Avo::Concerns::ControlsPlacement
include Avo::Concerns::HasDiscreetInformation

# Avo::Current methods
delegate :context, to: Avo::Current
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/avo/resources/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Avo::Resources::Event < Avo::BaseResource
self.profile_photo = {
source: :profile_photo
}
self.discreet_information = :timestamps

def fields
field :name, as: :text, link_to_record: true, sortable: true, stacked: true
Expand Down
13 changes: 13 additions & 0 deletions spec/dummy/app/avo/resources/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ class Avo::Resources::Post < Avo::BaseResource
main_app.post_path(record)
}

self.discreet_information = [
:timestamps,
{
tooltip: -> { sanitize("Product is <strong>#{record.published_at ? "published" : "draft"}</strong>", tags: %w[strong]) },
icon: -> { "heroicons/outline/#{record.published_at ? "eye" : "eye-slash"}" }
},
{
label: -> { record.published_at ? "✅" : "🙄" },
url: -> { "https://avohq.io" },
url_target: :_blank
}
]

def fields
field :id, as: :id
field :name, required: true, sortable: true
Expand Down
22 changes: 18 additions & 4 deletions spec/dummy/app/avo/resources/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ class Avo::Resources::Product < Avo::BaseResource
self.grid_view = {
card: -> do
{
cover_url: record.image.attached? ? main_app.url_for(record.image.variant(resize: "300x300")) : nil,
cover_url: record.image.attached? ? main_app.url_for(record.image.variant(resize_to_fill: [300, 300])) : nil,
title: record.title,
body: simple_format(record.description),
badge_label: (record.updated_at < 1.week.ago) ? "New" : "Updated",
badge_color: (record.updated_at < 1.week.ago) ? "green" : "orange",
badge_title: (record.updated_at < 1.week.ago) ? "New product here" : "Updated product here"
badge_label: record.status == :new ? "New" : "Updated",
adrianthedev marked this conversation as resolved.
Show resolved Hide resolved
badge_color: record.status == :new ? "green" : "orange",
adrianthedev marked this conversation as resolved.
Show resolved Hide resolved
badge_title: record.status == :new ? "New product here" : "Updated product here"
adrianthedev marked this conversation as resolved.
Show resolved Hide resolved
}
end,
html: -> do
Expand All @@ -28,6 +28,20 @@ class Avo::Resources::Product < Avo::BaseResource
self.index_query = -> {
query.includes image_attachment: :blob
}
self.discreet_information = [
{
tooltip: -> { sanitize("Product is <strong>#{record.status}</strong>", tags: %w[strong]) },
icon: "heroicons/outline/bold"
},
:timestamps
]
# self.discreet_information = {
# tooltip: -> { sanitize("Product is <strong>#{record.status}</strong>", tags: %w[strong]) },
# icon: "heroicons/outline/bold"
# }
self.profile_photo = {
source: -> { record.image.attached? ? main_app.url_for(record.image.variant(resize_to_fill: [300, 300])) : nil }
}

def fields
field :id, as: :id
Expand Down
4 changes: 4 additions & 0 deletions spec/dummy/app/models/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ class Product < ApplicationRecord

has_one_attached :image
has_many_attached :images

def status
(id % 2).zero? ? :new : :updated
end
end
1 change: 1 addition & 0 deletions spec/dummy/config/locales/avo.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ en:
delete_item: Delete %{item}
detach_item: detach %{item}
details: details
discreet_information_timestamps_html: "<div style=\"text-align: right;\">Created at %{created_at} <br/>Updated at %{updated_at}</div>"
download: Download
download_file: Download file
download_item: Download %{item}
Expand Down
Loading