Skip to content

Commit

Permalink
Merge pull request #16040 from opf/implementation/55181-replace-sidem…
Browse files Browse the repository at this point in the history
…enu-of-notification-center-with-rails-component

[55181] Replace sidemenu of notification center with Rails component
  • Loading branch information
oliverguenther authored Jul 11, 2024
2 parents e9c5666 + e5c7eac commit 75206e9
Show file tree
Hide file tree
Showing 59 changed files with 480 additions and 809 deletions.
1 change: 1 addition & 0 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import "shares/invite_user_form_component"
@import "work_packages/progress/modal_body_component"
@import "open_project/common/attribute_component"
@import "open_project/common/submenu_component"
@import "filter/filters_component"
@import "projects/row_component"
@import "settings/project_custom_fields/project_custom_field_mapping/new_project_mapping_component"
90 changes: 57 additions & 33 deletions app/components/open_project/common/submenu_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,69 +1,93 @@
<div class="op-sidebar" data-controller="filter--filter-list" data-application-target="dynamic">
<div class="op-submenu" data-controller="filter--filter-list" data-application-target="dynamic" data-test-selector="op-submenu">
<% if @searchable %>
<div class="op-sidebar--search">
<div class="op-submenu--search">
<%= render Primer::Alpha::TextField.new(name: "search",
label: I18n.t("label_search"),
placeholder: I18n.t("label_search"),
leading_visual: { icon: :search },
visually_hide_label: true,
classes: "op-sidebar--search-input",
classes: "op-submenu--search-input",
data: {
action: "input->filter--filter-list#filterLists",
"filter--filter-list-target": "filter",
"test-selector": "op-sidebar--search-input"
"test-selector": "op-submenu--search-input"
},) %>

<%= render Primer::Beta::Text.new(display: :none,
classes: "op-sidebar--search-no-results-container",
classes: "op-submenu--search-no-results-container",
data: {
"test-selector": "op-sidebar--search-no-results",
"test-selector": "op-submenu--search-no-results",
"filter--filter-list-target": "noResultsText",
}) do
I18n.t("js.autocompleter.notFoundText")
end %>
</div>
<% end %>

<div class="op-sidebar--body" data-test-selector="op-sidebar--body">
<div class="op-submenu--body" data-test-selector="op-submenu--body">
<% if top_level_sidebar_menu_items.any? %>
<div class="op-sidemenu">
<ul class="op-sidemenu--items">
<% top_level_sidebar_menu_items.first.children.each do |menu_item| %>
<li class="op-sidemenu--item" data-filter--filter-list-target="searchItem">
<% selected = menu_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= menu_item.href %>" data-test-selector="op-sidemenu--item-action">
<span class="op-sidemenu--item-title"><%= menu_item.title %></span>
</a>
</li>
<% end %>
</ul>
</div>
<ul class="op-submenu--items">
<% top_level_sidebar_menu_items.first.children.each do |menu_item| %>
<li class="op-submenu--item" data-filter--filter-list-target="searchItem">
<% selected = menu_item.selected ? 'selected' : '' %>
<a class="op-submenu--item-action <%= selected %>" href="<%= menu_item.href %>" data-test-selector="op-submenu--item-action">
<% if menu_item.icon %>
<%= render Primer::Beta::Octicon.new(icon: menu_item.icon, classes: "op-submenu--item-icon", mr: 2) %>
<% end %>
<span class="op-submenu--item-title">
<%= menu_item.title %>

<% if menu_item.show_enterprise_icon %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
<% end %>
</span>

<% if menu_item.count %>
<span class="op-bubble op-bubble_alt_highlighting" data-test-selector="op-submenu--item-count"><%= menu_item.count %></span>
<% end %>
</a>
</li>
<% end %>
</ul>
<% end %>

<% nested_sidebar_menu_items.each do |menu_item| %>
<div class="op-sidemenu"
data-controller="menus--expandable-sidemenu"
<div data-controller="menus--submenu"
data-application-target="dynamic">

<button class="op-sidemenu--title"
<button class="op-submenu--title"
type="button"
data-action="click->menus--expandable-sidemenu#toggleContainer">
data-action="click->menus--submenu#toggleContainer">
<%= menu_item.header %>
<span class="icon-small icon-arrow-up1"
aria-hidden="true"
data-menus--expandable-sidemenu-target="indicator">
data-menus--submenu-target="indicator">
</span>
</button>

<ul class="op-sidemenu--items"
data-menus--expandable-sidemenu-target="container">
<ul class="op-submenu--items"
data-menus--submenu-target="container">
<% menu_item.children.each do |child_item| %>
<li class="op-sidemenu--item" data-filter--filter-list-target="searchItem">
<li class="op-submenu--item" data-filter--filter-list-target="searchItem">
<% selected = child_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>" data-test-selector="op-sidemenu--item-action">
<span class="op-sidemenu--item-title"><%= child_item.title %></span>
<% if child_item.favored %>
<%= render Primer::Beta::Octicon.new(icon: "star-fill", "aria-label": I18n.t(:label_favorite), classes: %w[op-sidemenu--item-mark op-primer--star-icon]) %>
<a class="op-submenu--item-action <%= selected %>" href="<%= child_item.href %>" data-test-selector="op-submenu--item-action">
<% if child_item.icon %>
<%= render Primer::Beta::Octicon.new(icon: child_item.icon, classes: "op-submenu--item-icon", mr: 2) %>
<% end %>
<span class="op-submenu--item-title">
<%= child_item.title %>

<% if child_item.show_enterprise_icon %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
<% end %>

<% if child_item.favored %>
<%= render Primer::Beta::Octicon.new(icon: "star-fill", "aria-label": I18n.t(:label_favorite), classes: %w[op-submenu--item-mark op-primer--star-icon], ml: 2) %>
<% end %>
</span>

<% if child_item.count %>
<span class="op-bubble op-bubble_alt_highlighting" data-test-selector="op-submenu--item-count"><%= child_item.count %></span>
<% end %>
</a>
</li>
Expand All @@ -74,12 +98,12 @@
</div>

<% if @create_btn_options.present? %>
<div class="op-sidebar--footer">
<div class="op-submenu--footer">
<%= render Primer::Beta::Button.new(scheme: :primary,
tag: :a,
href: @create_btn_options[:href],
test_selector: "#{@create_btn_options[:module_key]}--create-button",
classes: "op-sidebar--footer-action") do |button|
classes: "op-submenu--footer-action") do |button|
button.with_leading_visual_icon(icon: "plus")
if @create_btn_options[:btn_text].present?
@create_btn_options[:btn_text]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
.op-sidemenu
.op-submenu
height: 100%
display: flex
flex-direction: column
overflow: hidden

&--search
margin: 12px
color: var(--main-menu-font-color)
display: flex
flex-direction: column
row-gap: 16px

&--search-input
// Overriding the defult input styles, because of the default dark background of the sidebar
color: var(--main-menu-font-color) !important
border-color: var(--borderColor-muted) !important

&--body
flex-grow: 1
overflow: auto
@include styled-scroll-bar

&:only-child
padding-bottom: 10px

&--footer
display: grid
text-align: center
padding: 1rem

@supports (-webkit-touch-callout: none)
padding: 1rem 1rem 5rem

.button .spot-icon
margin-right: 0.5rem

&--title
display: flex
justify-content: space-between
Expand Down Expand Up @@ -46,16 +82,10 @@
padding-left: 12px

&--item-title
flex-grow: 1
display: inline-block
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
line-height: 30px
text-decoration: none

&--item-icon
font-size: 24px
margin-right: 8px

&--item-mark
margin-left: 8px
42 changes: 42 additions & 0 deletions app/controllers/notifications/menus_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2023 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.
# ++
module Notifications
class MenusController < ApplicationController
# No authorize as every user (or logged in user)
# is allowed to see the menu.
no_authorization_required! :show

def show
menu = Notifications::Menu.new(params:, current_user:)

@sidebar_menu_items = menu.menu_items

render layout: nil
end
end
end
10 changes: 5 additions & 5 deletions app/menus/members/menu.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def user_status_options
OpenProject::Menu::MenuItem.new(title: I18n.t("members.menu.all"),
href: project_members_path(project),
selected: active_filter_count == 0),
menu_item(I18n.t("members.menu.locked"), status: :locked),
menu_item(I18n.t("members.menu.invited"), status: :invited)
menu_item(title: I18n.t("members.menu.locked"), query_params: { status: :locked }),
menu_item(title: I18n.t("members.menu.invited"), query_params: { status: :invited })
]
end

Expand All @@ -57,13 +57,13 @@ def project_roles_entries
.where(id: MemberRole.where(member_id: @project.members.select(:id)).select(:role_id))
.distinct
.pluck(:id, :name)
.map { |id, name| menu_item(name, role_id: id) }
.map { |id, name| menu_item(title: name, query_params: { role_id: id }) }
end

def permission_menu_entries
Members::UserFilterComponent
.share_options
.map { |name, id| menu_item(name, shared_role_id: id) }
.map { |name, id| menu_item(title: name, query_params: { shared_role_id: id }) }
end

def project_group_entries
Expand All @@ -72,7 +72,7 @@ def project_group_entries
.order(lastname: :asc)
.distinct
.pluck(:id, :lastname)
.map { |id, name| menu_item(name, group_id: id) }
.map { |id, name| menu_item(title: name, query_params: { group_id: id }) }
end

def selected?(query_params)
Expand Down
Loading

0 comments on commit 75206e9

Please sign in to comment.