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

[#55581] popover for user information on hover #17255

Draft
wants to merge 67 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
44ac6fc
[#55581] enforce hover card in project member list for now
EinLama Oct 30, 2024
937fa1e
[#55581] unwrap quotes for angular components
EinLama Oct 31, 2024
8f324fa
[#55581] return early
EinLama Nov 11, 2024
b743a60
[#55581] Users::HoverCardComponent
EinLama Nov 11, 2024
2528268
[#55581] prefer flex layout
EinLama Nov 11, 2024
530d722
WIP
EinLama Nov 11, 2024
867d204
[#55581] offer hover card trigger switch in avatar helper
EinLama Nov 11, 2024
8c798a8
[#55581] first draft for triangle pointing at hover source
EinLama Nov 11, 2024
5ee7c09
[#55581] construct project membership string, link to profile
EinLama Nov 12, 2024
3629b10
avatar details
EinLama Nov 12, 2024
44ced5b
[#55581] show email if allowed to do so
EinLama Nov 12, 2024
ed8f9e2
[#55581] link project names to projects
EinLama Nov 12, 2024
6199acd
[#55581] attach hover card attributes to avatar image
EinLama Nov 12, 2024
3a0e2f8
[#55581] copyright notices
EinLama Nov 13, 2024
ad59277
[#55581] clear timeout for closing hover card
EinLama Nov 13, 2024
1082109
return as soon as possible
EinLama Nov 13, 2024
d5f0b38
[#55581] set position & close delay of card by data-attr
EinLama Nov 13, 2024
375c870
[#55581] do not show the same modal twice
EinLama Nov 13, 2024
1450c90
[#55581] link to groups instead of projects
EinLama Nov 15, 2024
0e91b52
[#55581] link the end of the sentence to users#show
EinLama Nov 15, 2024
0360982
[#55581] allow hovercards in auto completer and share dialog
EinLama Nov 19, 2024
12f7a7d
[#55581] only show hovercard if set active
EinLama Nov 19, 2024
aeb29e9
[#55581] do not attempt to show hover cards for groups
EinLama Nov 19, 2024
3abcc0b
[#55581] ensure hover card is rendered in front of modals
EinLama Nov 19, 2024
537126d
[#55581] show hover card in member list
EinLama Nov 20, 2024
5f9fbb8
WIP
EinLama Nov 20, 2024
7e976d4
[#55581] make portal target customizable
EinLama Nov 21, 2024
1c3ef02
default should be default
EinLama Nov 21, 2024
1dcc011
[#55581] provide hover card in news module
EinLama Nov 21, 2024
5bacf83
[#55581] hover card on boards
EinLama Nov 21, 2024
75197e8
[#55581] hover card in wp activities
EinLama Nov 21, 2024
ed817a7
[#55581] hover card in meetings
EinLama Nov 21, 2024
f3be0fd
[#55581] hover card on project overview page (members widget)
EinLama Nov 22, 2024
1fa7ed1
[#55581] fix z-index on top of auto completers
EinLama Nov 22, 2024
c8f132d
[#55581] small polishing fixes
EinLama Nov 22, 2024
4715127
[#55581] do not set top alignment by default
EinLama Nov 22, 2024
b1c97b4
[#55581] add necessary translation for hover card permission
EinLama Nov 22, 2024
33a7126
[#55581] remove redundant comments, provide 404 locale
EinLama Nov 22, 2024
f7a10fa
frozen_string_literal
EinLama Nov 22, 2024
8e6fc05
[#55581] explain missing urls in some cases
EinLama Nov 22, 2024
fd5d811
[#55581] remove unneeded permission
EinLama Nov 22, 2024
dfe0f5a
mv project group
EinLama Nov 22, 2024
997fe53
[#55581] do not show hover card for placeholder users
EinLama Nov 22, 2024
6355a2b
[#55581] reduce ABC in avatar helper
EinLama Nov 22, 2024
36d5a84
[#55581] spec for user hover cards on member page
EinLama Nov 22, 2024
cb906e3
[#55581] reduce email font size
EinLama Nov 22, 2024
d3ecb66
[#55581] do not let very long names/mails overflow
EinLama Nov 22, 2024
8385f02
[#55581] wait for one second before displaying a hover card
EinLama Nov 25, 2024
1e40f90
[#55581] remove superfluous styling
EinLama Nov 25, 2024
2461c0d
[#55581] link to edit profile, localize button string
EinLama Nov 25, 2024
a465d59
[#55581] use text-shortener mixin
EinLama Nov 25, 2024
fb61671
[#55581] remove alignment and close delay options
EinLama Nov 25, 2024
5db32b7
[#55581] make user hover card opt-out
EinLama Nov 25, 2024
1fb00f3
fix indentation
EinLama Nov 25, 2024
49aec8b
[#55581] allow disabling the hover card
EinLama Nov 25, 2024
0f0ff98
[#55581] disable hover card on hover card
EinLama Nov 25, 2024
945e505
[#55581] only show visible groups within the hover card
EinLama Nov 26, 2024
6e25f7d
[#55581] hover cards only in project share, not wp share
EinLama Nov 26, 2024
eee22e3
[#55581] fix bug where hover cards are sometimes directly dismissed
EinLama Nov 26, 2024
4843e8a
[#55581] do not try to show a modal twice at the same time
EinLama Nov 26, 2024
3a95189
[#55581] use test selectors in spec
EinLama Nov 26, 2024
fdc2b1b
[#55581] specs for Users::HoverCardComponent
EinLama Nov 26, 2024
6a6a0a6
[#55581] fix avatar_helper specs
EinLama Nov 26, 2024
62d92bb
[#55581] do not show the hover card within the log time dialog
EinLama Nov 27, 2024
56ed83a
[#55581] ensure the URL is a string
EinLama Nov 27, 2024
7679081
[#55581] disable hover cards in global search
EinLama Nov 27, 2024
9c47122
[#55581] lookbook proposal
EinLama Nov 27, 2024
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
1 change: 1 addition & 0 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
@import "op_primer/border_box_table_component"
@import "work_packages/exports/modal_dialog_component"
@import "work_package_relations_tab/index_component"
@import "users/hover_card_component"
2 changes: 1 addition & 1 deletion app/components/shares/invite_user_form_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
) do |form|
grid_layout('invite-user-form', tag: :div) do |invite_form|
invite_form.with_area('invitee') do
render(Shares::Invitee.new(form))
render(Shares::Invitee.new(form, allow_hover_cards:))
end

invite_form.with_area('permission') do
Expand Down
5 changes: 3 additions & 2 deletions app/components/shares/invite_user_form_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ class InviteUserFormComponent < ApplicationComponent # rubocop:disable OpenProje
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

attr_reader :entity, :strategy, :errors
attr_reader :entity, :strategy, :errors, :allow_hover_cards

def initialize(strategy:, errors: nil)
def initialize(strategy:, errors: nil, allow_hover_cards: false)
super

@strategy = strategy
@entity = strategy.entity
@errors = errors
@allow_hover_cards = allow_hover_cards
end

def new_share
Expand Down
9 changes: 7 additions & 2 deletions app/components/shares/manage_shares_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<%=
if strategy.manageable?
modal_content.with_row do
render(Shares::InviteUserFormComponent.new(strategy:, errors: errors))
render(Shares::InviteUserFormComponent.new(strategy:, errors:, allow_hover_cards:))
end
end

Expand Down Expand Up @@ -100,10 +100,15 @@
end
else
strategy.shares.each do |share|
render(Shares::ShareRowComponent.new(share:, strategy:, container: border_box))
render(Shares::ShareRowComponent.new(share:, strategy:, container: border_box, allow_hover_cards:))
end
end
end
end

if allow_hover_cards
modal_content.with_row do
helpers.angular_component_tag 'opce-custom-modal-overlay', class: 'op-user-share-modal-overlay'
end
end
%>
2 changes: 2 additions & 0 deletions app/components/shares/manage_shares_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ManageSharesComponent < ApplicationComponent # rubocop:disable OpenProject
attr_reader :strategy,
:entity,
:errors,
:allow_hover_cards,
:modal_content

def initialize(strategy:, modal_content:, errors: nil)
Expand All @@ -45,6 +46,7 @@ def initialize(strategy:, modal_content:, errors: nil)
@entity = strategy.entity
@errors = errors
@modal_content = modal_content
@allow_hover_cards = strategy.allow_hover_cards?
end

def self.wrapper_key
Expand Down
3 changes: 2 additions & 1 deletion app/components/shares/share_row_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
end

user_row_grid.with_area(:avatar, tag: :div) do
render(Users::AvatarComponent.new(user: principal, show_name: false, size: :medium))
render(Users::AvatarComponent.new(user: principal, show_name: false, size: :medium,
hover_card: { active: allow_hover_cards, target: :custom }))
end

user_row_grid.with_area(:user_details, tag: :div, classes: 'ellipsis') do
Expand Down
5 changes: 3 additions & 2 deletions app/components/shares/share_row_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ShareRowComponent < ApplicationComponent # rubocop:disable OpenProject/Add
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(share:, strategy:, container: nil)
def initialize(share:, strategy:, container: nil, allow_hover_cards: false)
super

@share = share
Expand All @@ -45,6 +45,7 @@ def initialize(share:, strategy:, container: nil)
@principal = share.principal
@available_roles = strategy.available_roles
@container = container
@allow_hover_cards = allow_hover_cards
end

def wrapper_uniq_by
Expand All @@ -53,7 +54,7 @@ def wrapper_uniq_by

private

attr_reader :share, :entity, :principal, :container, :available_roles, :strategy
attr_reader :share, :entity, :principal, :container, :available_roles, :strategy, :allow_hover_cards

def share_editable?
@share_editable ||= User.current != share.principal && sharing_manageable?
Expand Down
15 changes: 11 additions & 4 deletions app/components/users/avatar_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ class AvatarComponent < ApplicationComponent
include AvatarHelper
include OpPrimer::ComponentHelpers

def initialize(user:, show_name: true, link: true, size: "default", classes: "", title: nil, name_classes: "")
def initialize(user:, show_name: true, link: true, size: "default", classes: "", title: nil, name_classes: "",
hover_card: { active: true, target: :default })
super

@user = user
@show_name = show_name
@link = link
@size = size
@title = title
@hover_card = hover_card
@classes = classes
@name_classes = name_classes
end
Expand All @@ -49,14 +51,19 @@ def render?
end

def call
helpers.avatar(
@user,
options = {
size: @size,
link: @link,
hide_name: !@show_name,
title: @title,
class: @classes,
name_classes: @name_classes
name_classes: @name_classes,
hover_card: @hover_card
}

helpers.avatar(
@user,
**options
)
end
end
Expand Down
85 changes: 85 additions & 0 deletions app/components/users/hover_card_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.

OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

See COPYRIGHT and LICENSE files for more details.

++#%>

<%=
if @user.present?
flex_layout(classes: 'op-user-hover-card', data: { test_selector: "user-hover-card-#{@user.id}" }) do |flex|
flex.with_row do
render(Users::AvatarComponent.new(user: @user, show_name: false, link: false, hover_card: { active: false }))
end

flex.with_row do
flex_layout(classes: 'op-user-hover-card--info') do |f|
f.with_column(classes: 'op-user-hover-card--name') do
render(Primer::Beta::Text.new(font_weight: :semibold, data: { test_selector: 'user-hover-card-name' })) do
@user.name
end
end

if show_email?
f.with_column(classes: 'op-user-hover-card--email') do
render(Primer::Beta::Text.new(font_size: :small,
color: :muted,
data: { test_selector: 'user-hover-card-email' })) do
@user.mail
end
end
end
end
end

flex.with_row do
flex_layout(classes: 'op-user-hover-card--group-list') do |f|
f.with_column do
render(Primer::Beta::Octicon.new(icon: :people))
end

f.with_column do
render(Primer::Beta::Text.new(color: :muted, data: { test_selector: 'user-hover-card-groups' })) do
group_membership_summary
end
end
end
end

flex.with_row do
render(Primer::Beta::Button.new(tag: :a,
href: helpers.allowed_management_user_profile_path(@user),
data: { test_selector: 'user-hover-card-profile-btn' })) do
I18n.t("users.open_profile")
end
end
end
else
render Primer::Beta::Blankslate.new(border: false, narrow: true) do |component|
component.with_visual_icon(icon: "x-circle")
component.with_heading(tag: :h3).with_content(I18n.t("api_v3.errors.not_found.user"))
end
end
%>
95 changes: 95 additions & 0 deletions app/components/users/hover_card_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class Users::HoverCardComponent < ApplicationComponent
include OpPrimer::ComponentHelpers

def initialize(id:)
super

@id = id
@user = User.find(@id)
end

def show_email?
(@user == User.current) || User.current.allowed_globally?(:view_user_email)
end

# Constructs a string in the form of:
# "Member of group4, group5"
# or
# "Member of group1, group2 and 3 more"
# The latter string is cut off since the complete list of group names would exceed the allowed `max_length`.
def group_membership_summary(max_length = 40)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the value 40 coming from?

Copy link
Contributor Author

@EinLama EinLama Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A true magic number 🔮🧙🏻‍♂️ found by, uh... guessing and trying it out! 🪄

Seemed sensible when used with the English translation and should look okay with most other languages. The caveat is that only the group names are considered when deciding where to shorten the list. To make it perfect, we'd have to load the translation first, factor it's length into the calculation and THEN decide where to cut off the group list. I figured the value 40 is good enough in most cases and strikes a balance between pleasant looks and code complexity :)

groups = @user.groups.visible
return no_group_text if groups.empty?

group_links = linked_group_names(groups)

cutoff_index = calculate_cutoff_index(groups.map(&:name), max_length)
build_summary(group_links, cutoff_index)
end

private

def linked_group_names(groups)
groups.map { |group| link_to(h(group.name), show_group_path(group)) }
end

def no_group_text
t("users.groups.no_results_title_text")
end

# Calculate the index at which to cut off the group names, based on plain text length
def calculate_cutoff_index(names, max_length)
current_length = 0

names.each_with_index do |name, index|
new_length = current_length + name.length + (index > 0 ? 2 : 0) # 2 for ", " separator
return index if new_length > max_length

current_length = new_length
end

names.size # No cutoff needed -> return the total size
end

def build_summary(links, cutoff_index)
summary_links = safe_join(links[0...cutoff_index], ", ")
remaining_count = links.size - cutoff_index
remaining_count_link = link_to(t("users.groups.more", count: remaining_count), user_path(@user))

if remaining_count > 0
t("users.groups.summary_with_more", names: summary_links, count_link: remaining_count_link).html_safe
else
t("users.groups.summary", names: summary_links).html_safe
end
end
end
16 changes: 16 additions & 0 deletions app/components/users/hover_card_component.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Correct the z-index of the regular hover card container so that it is above the dropdown of user auto completers
.spot-modal-overlay:has(.op-user-hover-card)
z-index: 9600

.op-user-hover-card
gap: 1rem
overflow: hidden

.op-user-hover-card--info
gap: 0.5rem

.op-user-hover-card--name, .op-user-hover-card--email
@include text-shortener()

.op-user-hover-card--group-list
gap: 0.5rem
37 changes: 37 additions & 0 deletions app/controllers/users/hover_card_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class Users::HoverCardController < ApplicationController
no_authorization_required! :show

def show
@id = params[:id]
render layout: nil
end
end
Loading
Loading