Skip to content

Commit

Permalink
Merge pull request #15291 from opf/feature/49060-group-agenda-items-w…
Browse files Browse the repository at this point in the history
…ith-sections

Feature/49060 group agenda items with sections
  • Loading branch information
jjabari-op authored May 8, 2024
2 parents 1090e2d + d6474fc commit de3feec
Show file tree
Hide file tree
Showing 56 changed files with 2,454 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,11 @@ export default class extends Controller {
focusInput():void {
const titleInput = this.element.querySelector('input[name="meeting_agenda_item[title]"]');

setTimeout(() => {
this.element.scrollIntoView({ block: 'center' });
if (titleInput) {
(titleInput as HTMLInputElement).focus();
}
}, 100);
this.element.scrollIntoView({ block: 'center' });
if (titleInput) {
(titleInput as HTMLInputElement).focus();
this.setCursorAtEnd(titleInput as HTMLInputElement);
}
}

async cancel() {
Expand All @@ -77,4 +76,11 @@ export default class extends Controller {
addNotes() {
this.notesInputTarget.classList.remove('d-none');
}

setCursorAtEnd(inputElement:HTMLInputElement):void {
if (document.activeElement === inputElement) {
const valueLength = inputElement.value.length;
inputElement.setSelectionRange(valueLength, valueLength);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 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.
* ++
*/

import * as Turbo from '@hotwired/turbo';
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
static values = {
cancelUrl: String,
};

declare cancelUrlValue:string;

connect():void {
this.focusInput();
}

focusInput():void {
const titleInput = this.element.querySelector('input[name="meeting_section[title]"]');

this.element.scrollIntoView({ block: 'center' });
(titleInput as HTMLInputElement).focus();
this.setCursorAtEnd(titleInput as HTMLInputElement);
}

async cancel() {
const response = await fetch(this.cancelUrlValue, {
method: 'GET',
headers: {
'X-CSRF-Token': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement).content,
Accept: 'text/vnd.turbo-stream.html',
},
});

if (response.ok) {
const text = await response.text();
Turbo.renderStreamMessage(text);
}
}

setCursorAtEnd(inputElement:HTMLInputElement):void {
if (document.activeElement === inputElement) {
const valueLength = inputElement.value.length;
inputElement.setSelectionRange(valueLength, valueLength);
}
}
}
1 change: 1 addition & 0 deletions modules/meeting/app/components/_index.sass
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import "./meeting_agenda_items/item_component/show_component.sass"
@import "./meeting_agenda_items/form_component.sass"
@import "./meeting_sections/header_component.sass"
@import "./meetings/sidebar/state_component.sass"
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
t("button_cancel")
end
end
flex.with_column do
render(MeetingAgendaItem::MeetingSectionForm.new(f, meeting_section: @meeting_section))
end
flex.with_column do
render(MeetingAgendaItem::Submit.new(f, type: @type))
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ class FormComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(meeting:, meeting_agenda_item:, method:, submit_path:, cancel_path:, type: :simple, display_notes_input: nil)
def initialize(meeting:, meeting_section:, meeting_agenda_item:, method:, submit_path:, cancel_path:, type: :simple,
display_notes_input: nil)
super

@meeting = meeting
@meeting_section = meeting_section
@meeting_agenda_item = meeting_agenda_item
@method = method
@submit_path = submit_path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def wrapper_arguments
scheme: :default,
data: {
id: @meeting_agenda_item.id,
"draggable-id": @meeting_agenda_item.id,
"draggable-type": "agenda-item",
"drop-url": drop_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def call
render(Primer::Box.new(pl: 3)) do
render(MeetingAgendaItems::FormComponent.new(
meeting: @meeting_agenda_item.meeting,
meeting_section: @meeting_agenda_item.meeting_section,
meeting_agenda_item: @meeting_agenda_item,
method: :put,
submit_path: meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item, format: :turbo_stream),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
end

if @meeting_agenda_item.duration_in_minutes.present? && @meeting_agenda_item.duration_in_minutes > 0
grid.with_area(:duration, Primer::Beta::Text, color: duration_color_scheme, mr: 1) do
grid.with_area(:duration, Primer::Beta::Text, color: duration_color_scheme, mr: 1, font_size: :small) do
I18n.t('datetime.distance_in_words.x_minutes_abbreviated', count: @meeting_agenda_item.duration_in_minutes)
end
end
Expand All @@ -71,8 +71,13 @@
test_selector: 'op-meeting-agenda-actions')
edit_action_item(menu) if @meeting_agenda_item.editable?
add_note_action_item(menu) if @meeting_agenda_item.editable? && @meeting_agenda_item.notes.blank?
unless first? && last?
menu.with_divider
move_actions(menu)
end
menu.with_divider
copy_action_item(menu)
move_actions(menu)
menu.with_divider
delete_action_item(menu)
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
<%=
component_wrapper(data: wrapper_data_attributes) do
# The borderBox needs to be `position: relative` because of bug #49853
# (The action menu items float somewhere on the page as soon as you have to scroll the Box in Firefox)
render(border_box_container) do |border_box|
if @meeting.agenda_items.empty? && @form_hidden
border_box.with_body(
scheme: :default
) do
render(Primer::Beta::Blankslate.new) do |component|
component.with_visual_icon(icon: :book)
component.with_heading(tag: :h2).with_content(t("text_meeting_empty_heading"))
component.with_description do
flex_layout do |flex|
flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_1") }
end
flex.with_row do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_2") }
end
end
end
end
end
else
first_and_last = [@meeting.agenda_items.first, @meeting.agenda_items.last]
flex_layout(mb: 3) do |flex|
flex.with_row(classes: 'dragula-container', id: insert_target_modifier_id, data: { 'allowed-drop-type': 'section' }.merge(drop_target_config) ) do
first_and_last = [@meeting.sections.first, @meeting.sections.last]
render(
MeetingAgendaItems::ItemComponent.with_collection(
@meeting.agenda_items.with_includes_to_render,
container: border_box,
MeetingSections::ShowComponent.with_collection(
@meeting.sections,
first_and_last:
)
)
end
border_box.with_row(p: 0, border_top: 0, id: insert_target_modifier_id) do
render(MeetingAgendaItems::NewComponent.new(meeting: @meeting, hidden: @form_hidden, type: @form_type))
if @meeting.agenda_items.empty? && @meeting.sections.empty?
flex.with_row do
render(border_box_container) do |border_box|
if @form_hidden
border_box.with_body(
scheme: :default
) do
render(Primer::Beta::Blankslate.new) do |component|
component.with_visual_icon(icon: :book)
component.with_heading(tag: :h2).with_content(t("text_meeting_empty_heading"))
component.with_description do
flex_layout do |flex|
flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_1") }
end
flex.with_row do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_2") }
end
end
end
end
end
end
border_box.with_row(p: 0, border_top: 0) do
render(MeetingAgendaItems::NewComponent.new(meeting: @meeting, hidden: @form_hidden, type: @form_type))
end
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ def initialize(meeting:, form_hidden: true, form_type: :simple)

def wrapper_data_attributes
{
controller: "meeting-agenda-item-drag-and-drop",
"application-target": "dynamic",
"target-tag": "ul"
controller: "generic-drag-and-drop",
"application-target": "dynamic"
}
end

def drop_target_config
{
"is-drag-and-drop-target": true,
"target-allowed-drag-type": "section" # the type of dragged items which are allowed to be dropped in this target
}
end

Expand All @@ -55,7 +61,7 @@ def insert_target_modified?
end

def insert_target_modifier_id
"meeting-agenda-items-new-item"
"meeting-section-new-item"
end
end
end
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
<%=
component_wrapper(class: "mt-3", style: 'position: relative') do
component_wrapper(style: "position: relative") do
render(Primer::Alpha::ActionMenu.new) do |component|
component.with_show_button(scheme: :primary, disabled: @disabled) do |button|
component.with_show_button(scheme: button_scheme, disabled: @disabled) do |button|
button.with_leading_visual_icon(icon: :plus)
t("button_add")
end
component.with_item(
label: t("activerecord.models.meeting_agenda_item", count: 1),
tag: :a,
content_arguments: {
href: new_meeting_agenda_item_path(@meeting, type: "simple"),
data: { 'turbo-stream': true }
href: new_meeting_agenda_item_path(@meeting, type: "simple", meeting_section_id: @meeting_section&.id),
data: { "turbo-stream": true }
}
)
component.with_item(
label: t("activerecord.models.work_package", count: 1),
tag: :a,
content_arguments: {
href: new_meeting_agenda_item_path(@meeting, type: "work_package"),
data: { 'turbo-stream': true }
href: new_meeting_agenda_item_path(@meeting, type: "work_package", meeting_section_id: @meeting_section&.id),
data: { "turbo-stream": true }
}
)
unless @meeting_section
component.with_item(
label: "Section",
tag: :a,
content_arguments: {
href: meeting_sections_path(@meeting),
data: { "turbo-stream": true, "turbo-method": :post }
}
)
end
end
end
%>
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,26 @@ class NewButtonComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(meeting:, meeting_agenda_item: nil, disabled: false)
def initialize(meeting:, meeting_section: nil, disabled: false)
super

@meeting = meeting
@meeting_agenda_item = meeting_agenda_item || MeetingAgendaItem.new(meeting:, author: User.current)
@meeting_section = meeting_section
@disabled = @meeting.closed? || disabled
end

private

def wrapper_uniq_by
@meeting_section&.id
end

def render?
User.current.allowed_in_project?(:manage_agendas, @meeting.project)
end

def button_scheme
@meeting_section ? :secondary : :primary
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
render(MeetingAgendaItems::FormComponent.new(
meeting: @meeting,
meeting_agenda_item: @meeting_agenda_item,
meeting_section: @meeting_section,
method: :post,
submit_path: meeting_agenda_items_path(@meeting, format: :turbo_stream),
cancel_path: cancel_new_meeting_agenda_items_path(@meeting),
cancel_path: cancel_new_meeting_agenda_items_path(@meeting, meeting_section_id: @meeting_section&.id),
type: @type
))
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,22 @@ class NewComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(meeting:, meeting_agenda_item: nil, hidden: true, type: :simple)
def initialize(meeting:, meeting_section: nil, meeting_agenda_item: nil, hidden: true, type: :simple)
super

@meeting = meeting
@meeting_section = meeting_section
@meeting_agenda_item = meeting_agenda_item || build_agenda_item
@hidden = hidden
@type = type
end

private

def wrapper_uniq_by
@meeting_section&.id
end

def build_agenda_item
MeetingAgendaItem.new(
meeting: @meeting,
Expand Down
Loading

0 comments on commit de3feec

Please sign in to comment.