From 8bd7889d1f150edd73aa63a970644dee77ecfc8b Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Mon, 7 Oct 2024 09:20:51 +0200 Subject: [PATCH] Primerize statuses create/edit form Except the color picker which still uses an angular component. Also fixed an issue where the "Default value" checkbox could disappear from the form after submitting with an error in the form. --- app/forms/application_form.rb | 12 ++ app/forms/statuses/form.rb | 122 ++++++++++++++++++ app/views/statuses/edit.html.erb | 7 +- app/views/statuses/new.html.erb | 7 +- .../colors/colors-autocompleter.component.ts | 6 +- .../open_project/forms/color_select.html.erb | 10 ++ lib/primer/open_project/forms/color_select.rb | 24 ++++ .../forms/dsl/color_select_input.rb | 37 ++++++ .../open_project/forms/dsl/input_methods.rb | 4 + 9 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 app/forms/statuses/form.rb create mode 100644 lib/primer/open_project/forms/color_select.html.erb create mode 100644 lib/primer/open_project/forms/color_select.rb create mode 100644 lib/primer/open_project/forms/dsl/color_select_input.rb diff --git a/app/forms/application_form.rb b/app/forms/application_form.rb index 4438a280917..cb52b5b980f 100644 --- a/app/forms/application_form.rb +++ b/app/forms/application_form.rb @@ -39,4 +39,16 @@ class ApplicationForm < Primer::Forms::Base def url_helpers Rails.application.routes.url_helpers end + + # @return [ActiveRecord::Base] the model instance given to the form builder + def model + @builder.object + end + + # @param field_name [Symbol] the name of the attribute for which to retrieve + # the human-readable name + # @return [String] the human-readable name of the specified attribute + def attribute_name(field_name) + model.class.human_attribute_name(field_name) + end end diff --git a/app/forms/statuses/form.rb b/app/forms/statuses/form.rb new file mode 100644 index 00000000000..e454ddcc82e --- /dev/null +++ b/app/forms/statuses/form.rb @@ -0,0 +1,122 @@ +# 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. +#++ + +module Statuses + class Form < ApplicationForm + attr_reader :submit_label + + def initialize(submit_label: nil) + super() + + @submit_label = submit_label || I18n.t(:button_save) + end + + form do |statuses_form| + statuses_form.text_field( + label: attribute_name(:name), + name: :name, + required: true, + input_width: :medium + ) + + statuses_form.text_field( + label: attribute_name(:default_done_ratio), + name: :default_done_ratio, + required: true, + type: :number, + min: 0, + max: 100, + maxlength: 7, + autocomplete: "off", + input_width: :small + ) + + statuses_form.check_box( + label: attribute_name(:is_closed), + name: :is_closed + ) + + unless already_default_status? + statuses_form.check_box( + label: attribute_name(:is_default), + name: :is_default + ) + end + + statuses_form.check_box( + label: attribute_name(:is_readonly), + name: :is_readonly, + disabled: readonly_work_packages_restricted?, + caption: I18n.t("statuses.edit.status_readonly_html").html_safe + ) + + if readonly_work_packages_restricted? + statuses_form.html_content do + angular_component_tag "opce-enterprise-banner", + inputs: { + collapsible: true, + textMessage: t("text_wp_status_read_only_html"), + moreInfoLink: OpenProject::Static::Links.links[:enterprise_docs][:status_read_only][:href] + } + end + end + + statuses_form.check_box( + label: attribute_name(:excluded_from_totals), + name: :excluded_from_totals, + caption: I18n.t("statuses.edit.status_excluded_from_totals_text") + ) + + statuses_form.color_select_list( + label: attribute_name(:color_id), + name: :color_id, + caption: I18n.t("statuses.edit.status_color_text") + ) + + statuses_form.submit( + scheme: :primary, + name: :submit, + label: submit_label + ) + end + + def status + model + end + + def already_default_status? + status.is_default_was == true + end + + def readonly_work_packages_restricted? + !EnterpriseToken.allows_to?(:readonly_work_packages) + end + end +end diff --git a/app/views/statuses/edit.html.erb b/app/views/statuses/edit.html.erb index 4aa80f1331d..8bd69c03d6f 100644 --- a/app/views/statuses/edit.html.erb +++ b/app/views/statuses/edit.html.erb @@ -39,7 +39,8 @@ See COPYRIGHT and LICENSE files for more details. end %> -<%= labelled_tabular_form_for(@status) do |f| %> - <%= render partial: 'form', locals: { f: f } %> - <%= styled_button_tag t(:button_save), class: '-primary -with-icon icon-checkmark' %> +<%= error_messages_for :status %> + +<%= primer_form_with(model: @status) do |f| %> + <%= render Statuses::Form.new(f) %> <% end %> diff --git a/app/views/statuses/new.html.erb b/app/views/statuses/new.html.erb index 8b5b660713a..417e959dd7c 100644 --- a/app/views/statuses/new.html.erb +++ b/app/views/statuses/new.html.erb @@ -39,7 +39,8 @@ See COPYRIGHT and LICENSE files for more details. end %> -<%= labelled_tabular_form_for(@status) do |f| %> - <%= render partial: 'form', locals: { f: f } %> - <%= styled_button_tag t(:button_create), class: '-primary -with-icon icon-checkmark' %> +<%= error_messages_for :status %> + +<%= primer_form_with(model: @status) do |f| %> + <%= render Statuses::Form.new(f, submit_label: I18n.t(:button_create)) %> <% end %> diff --git a/frontend/src/app/shared/components/colors/colors-autocompleter.component.ts b/frontend/src/app/shared/components/colors/colors-autocompleter.component.ts index 0f988b6c8f9..498707f5e32 100644 --- a/frontend/src/app/shared/components/colors/colors-autocompleter.component.ts +++ b/frontend/src/app/shared/components/colors/colors-autocompleter.component.ts @@ -45,6 +45,7 @@ interface ColorItem { bindLabel="name" bindValue="value" [(ngModel)]="selectedOption" + [ngClass]="classes" (change)="onModelChange($event)" [clearable]="false" appendTo="body"> @@ -66,6 +67,8 @@ export class ColorsAutocompleterComponent implements OnInit { private highlightTextInline = false; + public classes:string; + private updateInputField:HTMLInputElement|undefined; private selectedColorId:string; @@ -80,7 +83,8 @@ export class ColorsAutocompleterComponent implements OnInit { this.setColorOptions(); this.updateInputField = document.getElementsByName(this.elementRef.nativeElement.dataset.updateInput as string)[0] as HTMLInputElement|undefined; - this.highlightTextInline = JSON.parse(this.elementRef.nativeElement.dataset.highlightTextInline as string) as boolean; + this.highlightTextInline = JSON.parse(this.elementRef.nativeElement.dataset.highlightTextInline || 'false') as boolean; + this.classes = this.elementRef.nativeElement.dataset.classes || ''; } public onModelChange(color:{ name:string, value:string }) { diff --git a/lib/primer/open_project/forms/color_select.html.erb b/lib/primer/open_project/forms/color_select.html.erb new file mode 100644 index 00000000000..ba1af96a830 --- /dev/null +++ b/lib/primer/open_project/forms/color_select.html.erb @@ -0,0 +1,10 @@ +<%= render(FormControl.new(input: @input)) do %> + <%= builder.hidden_field :color_id %> + <%= angular_component_tag("opce-colors-autocompleter", + class: "colors-autocomplete form--select-container -middle", + data: { + colors:, + classes: "ng-select--primerized", + update_input: "#{builder.object_name}[color_id]" + }) %> +<% end %> diff --git a/lib/primer/open_project/forms/color_select.rb b/lib/primer/open_project/forms/color_select.rb new file mode 100644 index 00000000000..35721b81b3c --- /dev/null +++ b/lib/primer/open_project/forms/color_select.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + # :nodoc: + class ColorSelect < Primer::Forms::BaseComponent + include AngularHelper + include ColorsHelper + + delegate :builder, :form, to: :@input + + def initialize(input:) + super() + @input = input + end + + def colors + options_for_colors(builder.object) + end + end + end + end +end diff --git a/lib/primer/open_project/forms/dsl/color_select_input.rb b/lib/primer/open_project/forms/dsl/color_select_input.rb new file mode 100644 index 00000000000..754f8c9559c --- /dev/null +++ b/lib/primer/open_project/forms/dsl/color_select_input.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + module Dsl + # :nodoc: + class ColorSelectInput < Primer::Forms::Dsl::Input + attr_reader :name, :label, :select_arguments + + def initialize(name:, label:, **system_arguments) + @name = name + @label = label + + super(**system_arguments) + end + + def to_component + ColorSelect.new(input: self) + end + + # :nocov: + def type + :color_select_list + end + # :nocov: + + # :nocov: + def focusable? + true + end + # :nocov: + end + end + end + end +end diff --git a/lib/primer/open_project/forms/dsl/input_methods.rb b/lib/primer/open_project/forms/dsl/input_methods.rb index bfb569e38ad..e2151a300dd 100644 --- a/lib/primer/open_project/forms/dsl/input_methods.rb +++ b/lib/primer/open_project/forms/dsl/input_methods.rb @@ -25,6 +25,10 @@ module Primer add_input StorageManualProjectFolderSelectionInput.new(builder: @builder, form: @form, **) end + def color_select_list(**, &) + add_input ColorSelectInput.new(builder:, form:, **, &) + end + def html_content(**, &) add_input HtmlContentInput.new(builder: @builder, form: @form, **, &) end