diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 48c52ea..411d56f 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -3,7 +3,7 @@ class ContestsController < ApplicationController include ContestantsConcern before_action :set_contest, only: %i[ destroy show ] - before_action :set_settings_contest, only: %i[ settings_general_edit settings_general_update settings_offline_edit settings_offline_update settings_categories_edit ] + before_action :set_settings_contest, only: %i[ settings_general_edit settings_general_update settings_public_edit settings_public_update settings_onsite_edit settings_onsite_update settings_online_edit settings_online_update settings_categories_edit ] before_action :offline_setup, only: %i[ offline_new offline_create offline_edit offline_update offline_completed ] skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create offline_edit offline_update offline_completed ] @@ -24,7 +24,15 @@ class ContestsController < ApplicationController authorize @contest end - def settings_offline_edit + def settings_public_edit + authorize @contest + end + + def settings_onsite_edit + authorize @contest + end + + def settings_online_edit authorize @contest end @@ -42,13 +50,33 @@ class ContestsController < ApplicationController end end - def settings_offline_update + def settings_public_update authorize @contest - if @contest.update(settings_offline_params) - redirect_to "/contests/#{@contest.id}/settings/offline", notice: t("contests.edit.notice") + if @contest.update(settings_public_params) + redirect_to "/contests/#{@contest.id}/settings/public", notice: t("contests.edit.notice") else - render :settings_offline_edit, status: :unprocessable_entity + render :settings_public_edit, status: :unprocessable_entity + end + end + + def settings_onsite_update + authorize @contest + + if @contest.update(settings_onsite_params) + redirect_to "/contests/#{@contest.id}/settings/onsite", notice: t("contests.edit.notice") + else + render :settings_onsite_edit, status: :unprocessable_entity + end + end + + def settings_online_update + authorize @contest + + if @contest.update(settings_online_params) + redirect_to "/contests/#{@contest.id}/settings/online", notice: t("contests.edit.notice") + else + render :settings_online_edit, status: :unprocessable_entity end end @@ -210,10 +238,18 @@ class ContestsController < ApplicationController end def settings_general_params - params.expect(contest: [ :lang, :name, :duration, :public, :ranking_mode, :team, :allow_registration, :code ]) + params.expect(contest: [ :lang, :name, :duration, :team, :allow_registration ]) end - def settings_offline_params + def settings_public_params + params.expect(contest: [ :public, :ranking_mode ]) + end + + def settings_onsite_params + params.expect(contest: [ :code ]) + end + + def settings_online_params params.expect(contest: [ :offline_form ]) end diff --git a/app/policies/contest_policy.rb b/app/policies/contest_policy.rb index dfe842c..d26a6cb 100644 --- a/app/policies/contest_policy.rb +++ b/app/policies/contest_policy.rb @@ -47,11 +47,27 @@ class ContestPolicy < ApplicationPolicy edit? end - def settings_offline_edit? + def settings_public_edit? edit? end - def settings_offline_update? + def settings_public_update? + edit? + end + + def settings_onsite_edit? + edit? + end + + def settings_onsite_update? + edit? + end + + def settings_online_edit? + edit? + end + + def settings_online_update? edit? end diff --git a/app/views/application/_contest_nav.html.slim b/app/views/application/_contest_nav.html.slim index bd2002d..b442ced 100644 --- a/app/views/application/_contest_nav.html.slim +++ b/app/views/application/_contest_nav.html.slim @@ -1,35 +1,3 @@ -javascript: - async function copyExtensionUrlToClipboard() { - await navigator.clipboard.writeText("#{message_url}?token=#{@contest.generate_token_for(:token)}"); - alert("#{t("contests.show.url_copied")}"); - } - -.row.mb-4 - .col - - if @contest.public - a.btn.btn-success href="/public/#{@contest.slug}" - = t("contests.show.open_public_scoreboard") - - else - a.btn.btn-success.disabled - = t("contests.show.public_scoreboard_disabled") - - if @contest.offline_form && @contest.puzzles.length < 2 - a.ms-3.btn.btn-success href="/public/#{@contest.slug}/offline" - = t("contests.show.open_offline_form") - - else - a.ms-3.btn.btn-success.disabled - = t("contests.show.offline_form_disabled") - button.btn.btn-success.ms-3 onclick="copyExtensionUrlToClipboard()" - css: - button > svg { - margin-right: 2px; - margin-top: -3px; - } - - - - - =< t("contests.show.copy_extension_url") - .row .col ul.nav.nav-tabs.mb-4 @@ -43,11 +11,5 @@ javascript: a.nav-link class=active_page(contest_messages_path(@contest)) href=contest_messages_path(@contest) = t("messages.plural").capitalize li.nav-item - a.nav-link class=active_page("/contests/#{@contest.id}/settings/general") href="/contests/#{@contest.id}/settings/general" - = t("contests.form.general") - li.nav-item - a.nav-link class=active_page("/contests/#{@contest.id}/settings/offline") href="/contests/#{@contest.id}/settings/offline" - = t("contests.form.offline") - li.nav-item - a.nav-link class=active_page("/contests/#{@contest.id}/settings/categories") href="/contests/#{@contest.id}/settings/categories" - = t("contests.form.categories") \ No newline at end of file + a.nav-link class=active_page("/contests/#{@contest.id}/settings") href="/contests/#{@contest.id}/settings/general" + = t("contests.nav.settings") \ No newline at end of file diff --git a/app/views/application/_params_nav.html.slim b/app/views/application/_params_nav.html.slim new file mode 100644 index 0000000..cd95ea7 --- /dev/null +++ b/app/views/application/_params_nav.html.slim @@ -0,0 +1,18 @@ +.row + .col + ul.nav.nav-tabs.mb-4 + li.nav-item + a.nav-link class=active_page("/contests/#{@contest.id}/settings/general") href="/contests/#{@contest.id}/settings/general" + = t("contests.nav.general") + li.nav-item + a.nav-link class=active_page("/contests/#{@contest.id}/settings/public") href="/contests/#{@contest.id}/settings/public" + = t("contests.nav.public") + li.nav-item + a.nav-link class=active_page("/contests/#{@contest.id}/settings/onsite") href="/contests/#{@contest.id}/settings/onsite" + = t("contests.nav.onsite") + li.nav-item + a.nav-link class=active_page("/contests/#{@contest.id}/settings/online") href="/contests/#{@contest.id}/settings/online" + = t("contests.nav.online") + li.nav-item + a.nav-link class=active_page("/contests/#{@contest.id}/settings/categories") href="/contests/#{@contest.id}/settings/categories" + = t("contests.nav.categories") \ No newline at end of file diff --git a/app/views/contests/settings_categories_edit.html.slim b/app/views/contests/settings_categories_edit.html.slim index c6953d2..ae2350e 100644 --- a/app/views/contests/settings_categories_edit.html.slim +++ b/app/views/contests/settings_categories_edit.html.slim @@ -1,3 +1,10 @@ += render "params_nav" + +.row + .col + .alert.alert-primary role="alert" + = t("contests.nav.categories_description") + = form_with model: Category, url: "/contests/#{@contest.id}/categories" do |form| - if @contest.categories.size > 0 .row @@ -24,6 +31,6 @@ = form.text_field :name, autocomplete: "off", value: nil, class: "form-control" = form.label :name, class: "required" = t("activerecord.attributes.category.new") - .row.mt-3 + .row.mt-4 .col = form.submit t("helpers.buttons.add"), class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/contests/settings_general_edit.html.slim b/app/views/contests/settings_general_edit.html.slim index 210eac8..c7f58fa 100644 --- a/app/views/contests/settings_general_edit.html.slim +++ b/app/views/contests/settings_general_edit.html.slim @@ -1,3 +1,5 @@ += render "params_nav" + = form_with model: @contest, url: "/contests/#{@contest.id}/settings/general" do |form| .row.mt-2.mb-3 .col @@ -15,22 +17,12 @@ .form-floating = form.select :lang, Languages::AVAILABLE_LANGUAGES.map { |lang| [ lang[:name], lang[:id] ] }, {}, class: "form-select" = form.label :lang - .row.mb-3 - .col - .form-floating - = form.select :ranking_mode, Ranking::AVAILABLE_RANKING_MODES.map { |mode| [ mode[:name], mode[:id] ] }, {}, class: "form-select" - = form.label :ranking_mode .row.mt-2.mb-3 .col .form-floating = form.text_field :code, autocomplete: "off", class: "form-control" = form.label :code, class: "required" .form-text = t("activerecord.attributes.contest.code_description") - .row.mt-4.mb-3 - .col - .form-check.form-switch - = form.check_box :public, class: "form-check-input" - = form.label :public .row.mb-3 style="display: none" .col .form-check.form-switch diff --git a/app/views/contests/settings_offline_edit.html.slim b/app/views/contests/settings_offline_edit.html.slim deleted file mode 100644 index 5ce258f..0000000 --- a/app/views/contests/settings_offline_edit.html.slim +++ /dev/null @@ -1,25 +0,0 @@ -- if @contest.puzzles.length > 1 - .row - .col - .alert.alert-warning - = t("contests.form.offline_single_puzzle_warning") - -- if @contest.puzzles.length <= 1 - = form_with model: @contest, url: "/contests/#{@contest.id}/settings/offline" do |form| - .row.mt-2.mb-3 - .col - - if @contest.puzzles.length <= 1 - .form-check.form-switch - = form.check_box :offline_form, class: "form-check-input" - = form.label :offline_form - .form-text = t("activerecord.attributes.contest.offline_form_warning") - .form-text = t("activerecord.attributes.contest.offline_form_description") - - else - .form-check.form-switch - = form.check_box :offline_form_fake, class: "form-check-input", disabled: true - = form.label :offline_form - .form-text = t("activerecord.attributes.contest.offline_form_warning") - .form-text = t("activerecord.attributes.contest.offline_form_description") - .row.mt-4 - .col - = form.submit t("helpers.buttons.update"), class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/contests/settings_online_edit.html.slim b/app/views/contests/settings_online_edit.html.slim new file mode 100644 index 0000000..bc00b5f --- /dev/null +++ b/app/views/contests/settings_online_edit.html.slim @@ -0,0 +1,46 @@ += render "params_nav" + +javascript: + async function copyExtensionUrlToClipboard() { + await navigator.clipboard.writeText("#{message_url}?token=#{@contest.generate_token_for(:token)}"); + alert("#{t("contests.show.url_copied")}"); + } + +.row.mb-4.mt-2 + .col + - if @contest.offline_form && @contest.puzzles.length < 2 + a.btn.btn-success href="/public/#{@contest.slug}/offline" + = t("contests.show.open_offline_form") + - else + a.btn.btn-success.disabled + = t("contests.show.offline_form_disabled") + button.btn.btn-success.ms-3 onclick="copyExtensionUrlToClipboard()" + css: + button > svg { + margin-right: 2px; + margin-top: -3px; + } + + + + + =< t("contests.show.copy_extension_url") + += form_with model: @contest, url: "/contests/#{@contest.id}/settings/online" do |form| + .row.mt-2.mb-3 + .col + - if @contest.puzzles.length <= 1 + .form-check.form-switch + = form.check_box :offline_form, class: "form-check-input" + = form.label :offline_form + .form-text = t("activerecord.attributes.contest.offline_form_warning") + .form-text = t("activerecord.attributes.contest.offline_form_description") + - else + .form-check.form-switch + = form.check_box :offline_form_fake, class: "form-check-input", disabled: true + = form.label :offline_form + .form-text = t("activerecord.attributes.contest.offline_form_warning") + .form-text = t("activerecord.attributes.contest.offline_form_description") + .row.mt-4 + .col + = form.submit t("helpers.buttons.update"), class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/contests/settings_onsite_edit.html.slim b/app/views/contests/settings_onsite_edit.html.slim new file mode 100644 index 0000000..c458ae1 --- /dev/null +++ b/app/views/contests/settings_onsite_edit.html.slim @@ -0,0 +1,12 @@ += render "params_nav" + += form_with model: @contest, url: "/contests/#{@contest.id}/settings/general" do |form| + .row.mt-2.mb-3 + .col + .form-floating + = form.text_field :code, autocomplete: "off", class: "form-control" + = form.label :code, class: "required" + .form-text = t("activerecord.attributes.contest.code_description") + .row.mt-4 + .col + = form.submit t("helpers.buttons.update"), class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/contests/settings_public_edit.html.slim b/app/views/contests/settings_public_edit.html.slim new file mode 100644 index 0000000..fa89bb9 --- /dev/null +++ b/app/views/contests/settings_public_edit.html.slim @@ -0,0 +1,17 @@ += render "params_nav" + += form_with model: @contest, url: "/contests/#{@contest.id}/settings/public" do |form| + .row.mt-2 + .col + .form-check.form-switch + = form.check_box :public, class: "form-check-input" + = form.label :public + .row.mt-3 + .col + .form-floating + = form.select :ranking_mode, Ranking::AVAILABLE_RANKING_MODES.map { |mode| [ mode[:name], mode[:id] ] }, {}, class: "form-select" + = form.label :ranking_mode + + .row.mt-4 + .col + = form.submit t("helpers.buttons.update"), class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/layouts/authenticated.html.slim b/app/views/layouts/authenticated.html.slim index 3c3d51d..fbfe452 100644 --- a/app/views/layouts/authenticated.html.slim +++ b/app/views/layouts/authenticated.html.slim @@ -5,7 +5,7 @@ html body .container.mt-5 - if @current_user - .float-end style="margin-top: -8px;" + .float-end style="margin-top: -5px;" nav.navbar.bg-body-primary - if @current_user.admin a.navbar-brand href=users_path class="btn btn-light" style="margin-right: 0" @@ -43,12 +43,19 @@ html .toast-body = msg - h1.mb-4 + h1.mb-5 - if @contest && @contest.id.present? = @contest.name - if active_page("/public") == "active" && @action_path a.ms-4.btn.btn-primary href=@action_path style="margin-top: -6px" = t("helpers.buttons.refresh") + - if active_page("/contests") == "active" + - if @contest.public + a.ms-4.btn.btn-success href="/public/#{@contest.slug}" style="margin-top: -6px;" + = t("contests.show.open_public_scoreboard") + - else + a.ms-4.btn.btn-success.disabled style="margin-top: -6px;" + = t("contests.show.public_scoreboard_disabled") - else = @title diff --git a/app/views/messages/index.html.slim b/app/views/messages/index.html.slim index cd561f7..06ce732 100644 --- a/app/views/messages/index.html.slim +++ b/app/views/messages/index.html.slim @@ -3,6 +3,8 @@ .row.mb-4 .col + .alert.alert-primary + = t("messages.index.info") - if @messages.length == 0 .alert.alert-warning = t("messages.index.no_messages") diff --git a/app/views/puzzles/index.html.slim b/app/views/puzzles/index.html.slim index a175fa5..00b53c6 100644 --- a/app/views/puzzles/index.html.slim +++ b/app/views/puzzles/index.html.slim @@ -5,36 +5,38 @@ .col a.btn.btn-primary href=new_contest_puzzle_path(@contest) style="margin-top: -3px" | + #{t("helpers.buttons.add")} - table.table.table-striped.table-hover - thead - tr - th - = t("activerecord.attributes.puzzle.image") - th - = t("activerecord.attributes.puzzle.name") - th - = t("activerecord.attributes.puzzle.brand") - th - = t("activerecord.attributes.puzzle.pieces") - th - = t("activerecord.attributes.puzzle.hidden") - tbody - - @puzzles.each do |puzzle| - tr.align-middle scope="row" - td - = image_tag(puzzle.image, class: "img-fluid", style: "max-height: 128px;") if puzzle.image.attached? - td - = puzzle.name - td - = puzzle.brand - td - = puzzle.pieces - td - - if puzzle.hidden? - - - - - td - a.btn.btn-sm.btn-secondary href=edit_contest_puzzle_path(@contest, puzzle) - = t("helpers.buttons.edit") \ No newline at end of file + + .d-flex.flex-column style="overflow-y: auto" + table.table.table-striped.table-hover + thead + tr + th + = t("activerecord.attributes.puzzle.image") + th + = t("activerecord.attributes.puzzle.name") + th + = t("activerecord.attributes.puzzle.brand") + th + = t("activerecord.attributes.puzzle.pieces") + th + = t("activerecord.attributes.puzzle.hidden") + tbody + - @puzzles.each do |puzzle| + tr.align-middle scope="row" + td + = image_tag(puzzle.image, class: "img-fluid", style: "max-height: 128px;") if puzzle.image.attached? + td + = puzzle.name + td + = puzzle.brand + td + = puzzle.pieces + td + - if puzzle.hidden? + + + + + td + a.btn.btn-sm.btn-secondary href=edit_contest_puzzle_path(@contest, puzzle) + = t("helpers.buttons.edit") \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index a36b145..a7c64ed 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -193,14 +193,19 @@ en: notice: Contest updated title: Edit contest settings form: - categories: Participant categories - general: General parameters - offline: Offline participation offline_single_puzzle_warning: This is not available for contests with more than one puzzle index: title: Welcome %{username}! manage_contests: Manage my contests new_contest: Create a new contest + nav: + categories: Participant categories + categories_description: Once you add categories, you will be able to assign them to participants on their profiles, and a filter for categories will be available on the public scoreboard + general: General + online: Online contests + onsite: Onsite contests + public: Public scoreboard + settings: Settings new: notice: Contest added title: New jigsaw puzzle contest @@ -287,10 +292,11 @@ en: rank: Rank lib: ranking: - actual: First by number of pieces assembled, then by time + actual: First by number of pieces assembled, then by time (recommended if unsure) theorical: By time only (projected time calculated with the ppm count) messages: index: + info: This section is only used for contests that rely on the connection with Google Meet no_messages: No messages received yet convert: title: New completion diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 548083b..84e1d0c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -164,14 +164,19 @@ fr: notice: Concours modifié title: Paramètres du concours form: - categories: Catégories de participant.e.s - general: Paramètres généraux - offline: Participation hors-ligne offline_single_puzzle_warning: Ce n'est pas activable pour les concours avec plusieurs puzzles index: title: Bienvenue %{username} ! manage_contests: Mes concours de puzzle new_contest: Créer un nouveau concours + nav: + categories: Catégories de participant.e.s + categories_description: Après avoir ajouté des catégories, elles pourront être attributées aux participant.e.s sur leurs profils, et un filtre sera disponible sur le classement public + general: Général + online: Concours en ligne + onsite: Concours en présentiel + public: Classement public + settings: Paramètres new: notice: Concours ajouté title: Nouveau concours @@ -258,10 +263,11 @@ fr: rank: Rang lib: ranking: - actual: Par nombre de pièces assemblées, puis par temps + actual: Par nombre de pièces assemblées, puis par temps (recommandé) theorical: Par temps uniquement (temps projeté calculé à partir de la vitesse d'assemblage) messages: index: + info: Cette section n'est pertinente que pour les concours en ligne qui utilisent la connexion depuis Google Meet no_messages: Pas de messages reçus pour le moment convert: title: Ajout d'une complétion diff --git a/config/routes.rb b/config/routes.rb index fe112da..dc23263 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,8 +11,12 @@ Rails.application.routes.draw do resources :contests do get "settings/general", to: "contests#settings_general_edit" patch "settings/general", to: "contests#settings_general_update" - get "settings/offline", to: "contests#settings_offline_edit" - patch "settings/offline", to: "contests#settings_offline_update" + get "settings/public", to: "contests#settings_public_edit" + patch "settings/public", to: "contests#settings_public_update" + get "settings/onsite", to: "contests#settings_onsite_edit" + patch "settings/onsite", to: "contests#settings_onsite_update" + get "settings/online", to: "contests#settings_online_edit" + patch "settings/online", to: "contests#settings_online_update" get "settings/categories", to: "contests#settings_categories_edit" resources :categories, only: [ :create, :destroy ] resources :completions