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 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 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
8 changes: 2 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,6 @@ GEM
prettyprint (0.2.0)
prop_initializer (0.2.0)
zeitwerk (>= 2.6.18)
psych (5.2.3)
date
stringio
public_suffix (6.0.1)
puma (6.6.0)
nio4r (~> 2.0)
Expand Down Expand Up @@ -505,8 +502,7 @@ GEM
ffi (~> 1.0)
rbs (3.8.1)
logger
rdoc (6.12.0)
psych (>= 4.0.0)
rdoc (6.11.0)
redis (5.3.0)
redis-client (>= 0.22.0)
redis-client (0.23.2)
Expand Down Expand Up @@ -643,7 +639,6 @@ GEM
standard-performance (1.6.0)
lint_roller (~> 1.1)
rubocop-performance (~> 1.23.0)
stringio (3.1.2)
syntax_tree (6.2.0)
prettier_print (>= 1.2.0)
terminal-table (4.0.0)
Expand Down Expand Up @@ -698,6 +693,7 @@ GEM

PLATFORMS
arm64-darwin-23
arm64-darwin-24
x86_64-linux

DEPENDENCIES
Expand Down
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, **data(item)} do %>
<%= item.label if item.label.present? %> <%= helpers.svg item.icon, class: "text-2xl h-4" %>
<% end %>
<% end %>
</div>
27 changes: 27 additions & 0 deletions app/components/avo/discreet_information_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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

def data(item) = item.data || {}
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
4 changes: 2 additions & 2 deletions 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 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>
<div class="text-2xl tracking-normal font-semibold text-gray-800 items-center flex flex-1 text-center sm:text-left justify-center sm:justify-start" data-target="title">
<span class="block"><%= link_to_if @url.present?, @name, @url, target: @target, class: class_names("text-gray-800", @classes) %></span>
<%= body %>
</div>
4 changes: 4 additions & 0 deletions app/helpers/avo/turbo_stream_actions_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def avo_close_modal
target: Avo::MODAL_FRAME_ID,
template: @view_context.turbo_frame_tag(Avo::MODAL_FRAME_ID)
end

def avo_turbo_reload
turbo_stream_action_tag :turbo_reload
end
end
end

Expand Down
3 changes: 2 additions & 1 deletion app/javascript/avo.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Mousetrap.bind('r r r', () => {
// Capture scroll position
scrollTop = document.scrollingElement.scrollTop

Turbo.visit(window.location.href, { action: 'replace' })
window.StreamActions.turbo_reload()
})

function isMac() {
Expand Down 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
5 changes: 5 additions & 0 deletions app/javascript/js/custom-stream-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ StreamActions.close_filters_dropdown = function () {
document.querySelector('.filters-dropdown-selector').classList.add('hidden')
}

// Uses Turbo to refresh the page
StreamActions.turbo_reload = function () {
window.Turbo.visit(window.location.href, { action: 'replace' })
}

StreamActions.open_filter = function () {
const id = this.getAttribute('unique-id')
setTimeout(() => {
Expand Down
2 changes: 1 addition & 1 deletion app/views/avo/actions/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<%= turbo_frame_tag Avo::MODAL_FRAME_ID do %>
<div
data-controller="<%= ["action", @action.get_stimulus_controllers].join(" ") %>"
data-action-no-confirmation-value="<%= @action.no_confirmation %>"
data-action-no-confirmation-value="<%= @action.no_confirmation? %>"
data-action-resource-name-value="<%= @resource.model_key %>"
data-resource-id="<%= params[:id] %>"
class="hidden text-slate-800"
Expand Down
20 changes: 17 additions & 3 deletions lib/avo/base_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class BaseAction
include Avo::Concerns::HasActionStimulusControllers
include Avo::Concerns::Hydration

DATA_ATTRIBUTES = {turbo_frame: Avo::MODAL_FRAME_ID}

class_attribute :name, default: nil
class_attribute :message
class_attribute :confirm_button_label
Expand Down Expand Up @@ -59,8 +61,8 @@ def to_param
to_s
end

def link_arguments(resource:, arguments: {}, **args)
path = Avo::Services::URIService.parse(resource.record&.persisted? ? resource.record_path : resource.records_path)
def path(resource:, arguments: {}, **args)
Avo::Services::URIService.parse(resource.record&.persisted? ? resource.record_path : resource.records_path)
.append_paths("actions")
.append_query(
**{
Expand All @@ -70,8 +72,10 @@ def link_arguments(resource:, arguments: {}, **args)
}.compact
)
.to_s
end

[path, {turbo_frame: Avo::MODAL_FRAME_ID}]
def link_arguments(resource:, arguments: {}, **args)
[path(resource:, arguments:, **args), DATA_ATTRIBUTES]
end

# Encrypt the arguments so we can pass sensible data as a query param.
Expand Down Expand Up @@ -366,6 +370,16 @@ def disabled?
!enabled?
end

def no_confirmation?
Avo::ExecutionContext.new(
target: no_confirmation,
action: self,
resource: @resource,
view: @view,
arguments:
).handle
end

private

def add_message(body, type = :info)
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
62 changes: 62 additions & 0 deletions lib/avo/discreet_information.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
class Avo::DiscreetInformation
extend PropInitializer::Properties
include ActionView::Helpers::TagHelper

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)
return if record.created_at.blank? && record.updated_at.blank?

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

created_at_tag = if record.created_at.present?
I18n.t("avo.created_at_timestamp", created_at:)
end

updated_at_tag = if record.updated_at.present?
I18n.t("avo.updated_at_timestamp", updated_at:)
end

DiscreetInformationItem.new(
tooltip: tag.div([created_at_tag, updated_at_tag].compact.join(tag.br), style: "text-align: right;"),
icon: "heroicons/outline/clock"
)
end

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

args = {
record:,
resource:,
view:
}

DiscreetInformationItem.new(
tooltip: Avo::ExecutionContext.new(target: item[:tooltip], **args).handle,
icon: Avo::ExecutionContext.new(target: item[:icon], **args).handle,
url: Avo::ExecutionContext.new(target: item[:url], **args).handle,
url_target: Avo::ExecutionContext.new(target: item[:url_target], **args).handle,
data: Avo::ExecutionContext.new(target: item[:data], **args).handle,
label: Avo::ExecutionContext.new(target: item[:label], **args).handle
)
end

DiscreetInformationItem = Struct.new(:tooltip, :icon, :url, :url_target, :data, :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::HasHelpers
include Avo::Concerns::Hydration
include Avo::Concerns::Pagination
include Avo::Concerns::HasDiscreetInformation
include Avo::Concerns::RowControlsConfiguration

# Avo::Current methods
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/avo/actions/toggle_published.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Avo::Actions::TogglePublished < Avo::BaseAction
self.message = "Are you sure, sure?"
self.confirm_button_label = "Toggle"
self.cancel_button_label = "Don't toggle yet"
self.no_confirmation = -> { arguments[:no_confirmation] || false }

def fields
field :notify_user, as: :boolean, default: true
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

self.row_controls_config = {
float: true,
Expand Down
Loading
Loading