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

Merged
merged 79 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
3249503
[#55581] enforce hover card in project member list for now
EinLama Oct 30, 2024
b1e38ec
[#55581] unwrap quotes for angular components
EinLama Oct 31, 2024
7e72f62
[#55581] return early
EinLama Nov 11, 2024
fe106c3
[#55581] Users::HoverCardComponent
EinLama Nov 11, 2024
89587fd
[#55581] prefer flex layout
EinLama Nov 11, 2024
e7c16e7
WIP
EinLama Nov 11, 2024
8354574
[#55581] offer hover card trigger switch in avatar helper
EinLama Nov 11, 2024
ef22234
[#55581] first draft for triangle pointing at hover source
EinLama Nov 11, 2024
3f2ab1d
[#55581] construct project membership string, link to profile
EinLama Nov 12, 2024
ca90dbe
avatar details
EinLama Nov 12, 2024
277b9dc
[#55581] show email if allowed to do so
EinLama Nov 12, 2024
1f554f2
[#55581] link project names to projects
EinLama Nov 12, 2024
4f6fd0d
[#55581] attach hover card attributes to avatar image
EinLama Nov 12, 2024
8aff877
[#55581] copyright notices
EinLama Nov 13, 2024
928ab41
[#55581] clear timeout for closing hover card
EinLama Nov 13, 2024
811fbd3
return as soon as possible
EinLama Nov 13, 2024
b096073
[#55581] set position & close delay of card by data-attr
EinLama Nov 13, 2024
c37731e
[#55581] do not show the same modal twice
EinLama Nov 13, 2024
8ef3486
[#55581] link to groups instead of projects
EinLama Nov 15, 2024
869636e
[#55581] link the end of the sentence to users#show
EinLama Nov 15, 2024
3bef3eb
[#55581] allow hovercards in auto completer and share dialog
EinLama Nov 19, 2024
d334568
[#55581] only show hovercard if set active
EinLama Nov 19, 2024
ceb96aa
[#55581] do not attempt to show hover cards for groups
EinLama Nov 19, 2024
3246793
[#55581] ensure hover card is rendered in front of modals
EinLama Nov 19, 2024
792e1dd
[#55581] show hover card in member list
EinLama Nov 20, 2024
eb7bd5a
WIP
EinLama Nov 20, 2024
3a9b424
[#55581] make portal target customizable
EinLama Nov 21, 2024
87eeae3
default should be default
EinLama Nov 21, 2024
3e7d857
[#55581] provide hover card in news module
EinLama Nov 21, 2024
da077fa
[#55581] hover card on boards
EinLama Nov 21, 2024
450f2cb
[#55581] hover card in wp activities
EinLama Nov 21, 2024
3dc40b5
[#55581] hover card in meetings
EinLama Nov 21, 2024
ed0ff7f
[#55581] hover card on project overview page (members widget)
EinLama Nov 22, 2024
f1ebb2e
[#55581] fix z-index on top of auto completers
EinLama Nov 22, 2024
d83fb1a
[#55581] small polishing fixes
EinLama Nov 22, 2024
baf07d4
[#55581] do not set top alignment by default
EinLama Nov 22, 2024
dc77227
[#55581] add necessary translation for hover card permission
EinLama Nov 22, 2024
4269e4a
[#55581] remove redundant comments, provide 404 locale
EinLama Nov 22, 2024
8f54110
frozen_string_literal
EinLama Nov 22, 2024
e6d893e
[#55581] explain missing urls in some cases
EinLama Nov 22, 2024
af3b720
[#55581] remove unneeded permission
EinLama Nov 22, 2024
458bea9
mv project group
EinLama Nov 22, 2024
81df01e
[#55581] do not show hover card for placeholder users
EinLama Nov 22, 2024
9a9e7b2
[#55581] reduce ABC in avatar helper
EinLama Nov 22, 2024
ec9ffdf
[#55581] spec for user hover cards on member page
EinLama Nov 22, 2024
30a4481
[#55581] reduce email font size
EinLama Nov 22, 2024
f7d6d83
[#55581] do not let very long names/mails overflow
EinLama Nov 22, 2024
9e98465
[#55581] wait for one second before displaying a hover card
EinLama Nov 25, 2024
e5e8f60
[#55581] remove superfluous styling
EinLama Nov 25, 2024
b9d4fa5
[#55581] link to edit profile, localize button string
EinLama Nov 25, 2024
575a5c0
[#55581] use text-shortener mixin
EinLama Nov 25, 2024
2501760
[#55581] remove alignment and close delay options
EinLama Nov 25, 2024
52cdcdd
[#55581] make user hover card opt-out
EinLama Nov 25, 2024
90acc21
fix indentation
EinLama Nov 25, 2024
52f64f8
[#55581] allow disabling the hover card
EinLama Nov 25, 2024
4b5eba5
[#55581] disable hover card on hover card
EinLama Nov 25, 2024
3f22bd3
[#55581] only show visible groups within the hover card
EinLama Nov 26, 2024
5d18642
[#55581] hover cards only in project share, not wp share
EinLama Nov 26, 2024
c3d8532
[#55581] fix bug where hover cards are sometimes directly dismissed
EinLama Nov 26, 2024
2ad5f34
[#55581] do not try to show a modal twice at the same time
EinLama Nov 26, 2024
9d9faba
[#55581] use test selectors in spec
EinLama Nov 26, 2024
b063953
[#55581] specs for Users::HoverCardComponent
EinLama Nov 26, 2024
b1f0004
[#55581] fix avatar_helper specs
EinLama Nov 26, 2024
8fe734b
[#55581] do not show the hover card within the log time dialog
EinLama Nov 27, 2024
3db68a4
[#55581] ensure the URL is a string
EinLama Nov 27, 2024
5496530
[#55581] disable hover cards in global search
EinLama Nov 27, 2024
69a2eee
[#55581] lookbook proposal
EinLama Nov 27, 2024
b4fed15
[#55581] add exemplary user hover card to lookbook
EinLama Nov 28, 2024
7661b57
[#55581] reduce filesize of wp hover card image by 45%
EinLama Nov 28, 2024
f60f744
[#55581] user generic error message for missing users
EinLama Nov 28, 2024
ba84c3f
[#55581] do not try to access non-existent field
EinLama Nov 29, 2024
3c20a5f
[#55581] make hover card work with avatar fallbacks
EinLama Nov 29, 2024
8a1f7b9
[#55581] animate hover card slightly to make it feel less awkward
EinLama Nov 29, 2024
01d9bde
[#55581] deal with multiple hover triggers next to each other
EinLama Nov 29, 2024
ab9ea70
[#55581] prevent background layer from darkening in multi nested dialogs
EinLama Nov 29, 2024
f6ddf7f
Merge branch 'dev' into feature/55581-popover-for-user-information-on…
EinLama Dec 2, 2024
2462494
[#55581] use BEM style class definitions
EinLama Dec 2, 2024
4c17609
[#55581] use PathHelperService to construct hover card url
EinLama Dec 2, 2024
818876a
[#55581] remove unneede clean up of angular double quotes
EinLama Dec 2, 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
Binary file modified app/assets/images/lookbook/hover_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/lookbook/user_hover_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
86 changes: 86 additions & 0 deletions app/components/users/hover_card_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<%#-- 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")
# Show a generic error message to avoid leaking information
component.with_heading(tag: :h3).with_content(I18n.t("http.response.unexpected"))
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_by(id: @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
24 changes: 24 additions & 0 deletions app/components/users/hover_card_component.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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

// On the project list page, there is a bug when a hover card is invoked within the share dialog. The global spot
// modal overlay will darken the background while the hover card is active, since its semi-transparent bg shading is
// added on top of the other dialog background shaders. We don't want an additional spot modal background here,
// so we disable it for this edge case.
.controller-projects.action-index
.spot-modal-overlay:not(:has(.op-user-hover-card))
background: transparent

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

.op-user-hover-card--info
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.op-user-hover-card--info
&--info

this could be just &--info. This ensures that you're using the BEM style

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very nice, thanks! The previous class definitions were very cumbersome. Much better like this.

gap: 0.5rem

.op-user-hover-card--name, .op-user-hover-card--email
EinLama marked this conversation as resolved.
Show resolved Hide resolved
@include text-shortener()

.op-user-hover-card--group-list
EinLama marked this conversation as resolved.
Show resolved Hide resolved
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