diff --git a/app/app/assets/images/hero-1.jpg b/app/app/assets/images/hero-1.jpg index 80533f7..d03f39d 100644 Binary files a/app/app/assets/images/hero-1.jpg and b/app/app/assets/images/hero-1.jpg differ diff --git a/app/app/assets/images/hero-2.jpg b/app/app/assets/images/hero-2.jpg index 17c6650..fe4e5bc 100644 Binary files a/app/app/assets/images/hero-2.jpg and b/app/app/assets/images/hero-2.jpg differ diff --git a/app/app/assets/images/hero-3.jpg b/app/app/assets/images/hero-3.jpg index 3f03bc4..236be64 100644 Binary files a/app/app/assets/images/hero-3.jpg and b/app/app/assets/images/hero-3.jpg differ diff --git a/app/app/assets/images/hero-4.jpg b/app/app/assets/images/hero-4.jpg index 8d16296..46a8824 100644 Binary files a/app/app/assets/images/hero-4.jpg and b/app/app/assets/images/hero-4.jpg differ diff --git a/app/app/assets/images/hero-5.jpg b/app/app/assets/images/hero-5.jpg index cb6be8d..995cc09 100644 Binary files a/app/app/assets/images/hero-5.jpg and b/app/app/assets/images/hero-5.jpg differ diff --git a/app/app/assets/images/icon-setting.png b/app/app/assets/images/icon-setting.png new file mode 100644 index 0000000..db4ef0e Binary files /dev/null and b/app/app/assets/images/icon-setting.png differ diff --git a/app/app/assets/stylesheets/admin/field-value.css b/app/app/assets/stylesheets/admin/field-value.css index a47caf9..7d45981 100644 --- a/app/app/assets/stylesheets/admin/field-value.css +++ b/app/app/assets/stylesheets/admin/field-value.css @@ -5,7 +5,7 @@ } .admin-field-value-image { - width: 200px; + width: fit-content; height: 200px; border: 1px solid var(--default-border-color); } diff --git a/app/app/assets/stylesheets/admin/field.css b/app/app/assets/stylesheets/admin/field.css index 59224d0..2887349 100644 --- a/app/app/assets/stylesheets/admin/field.css +++ b/app/app/assets/stylesheets/admin/field.css @@ -1,7 +1,7 @@ .admin-field { - min-height: 2rem; display: flex; - gap: 1rem; + flex-direction: column; + gap: 0.25rem; .admin-field__label, .admin-field__value { display: flex; diff --git a/app/app/assets/stylesheets/admin/fields.css b/app/app/assets/stylesheets/admin/fields.css index 4e1e370..b50d3c4 100644 --- a/app/app/assets/stylesheets/admin/fields.css +++ b/app/app/assets/stylesheets/admin/fields.css @@ -1,5 +1,5 @@ .admin-fields { display: flex; flex-direction: column; - gap: 1rem; + gap: 2.0rem; } diff --git a/app/app/assets/stylesheets/admin/list.css b/app/app/assets/stylesheets/admin/list.css index 65a2417..223fd00 100644 --- a/app/app/assets/stylesheets/admin/list.css +++ b/app/app/assets/stylesheets/admin/list.css @@ -36,6 +36,17 @@ color: var(--color-gray-900); white-space: nowrap; } + + &.is-image { + height: fit-content; + } + + &.is-actions { + display: flex; + justify-content: flex-end; + gap: 1.0rem; + word-break: keep-all; + } } .admin-list-cell-with-icon { diff --git a/app/app/assets/stylesheets/common/text-link.css b/app/app/assets/stylesheets/common/text-link.css index c5622cc..ef178fe 100644 --- a/app/app/assets/stylesheets/common/text-link.css +++ b/app/app/assets/stylesheets/common/text-link.css @@ -18,5 +18,6 @@ &:hover { text-decoration: none; + cursor: pointer; } } diff --git a/app/app/assets/stylesheets/media/hero.css b/app/app/assets/stylesheets/media/hero.css index 0f58d88..3d6774f 100644 --- a/app/app/assets/stylesheets/media/hero.css +++ b/app/app/assets/stylesheets/media/hero.css @@ -26,26 +26,122 @@ bottom: 0; right: 0; opacity: 0; +} + +.hero-container { + + &[data-hero-image-count="1"] { + .hero-image { + animation: image-switch-animation 5s infinite; + &:nth-child(1) { animation-delay: 0s; } + } + } + + &[data-hero-image-count="2"] { + .hero-image { + animation: image-switch-animation 10s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + } + } + + &[data-hero-image-count="3"] { + .hero-image { + animation: image-switch-animation 15s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + } + } - animation: image-switch-animation 25s infinite; + &[data-hero-image-count="4"] { + .hero-image { + animation: image-switch-animation 20s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + } + } + + &[data-hero-image-count="5"] { + .hero-image { + animation: image-switch-animation 25s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + } + } - &:nth-child(1) { - animation-delay: 0s; + &[data-hero-image-count="6"] { + .hero-image { + animation: image-switch-animation 30s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + &:nth-child(6) { animation-delay: 25s; } + } } - &:nth-child(2) { - animation-delay: 5s; + &[data-hero-image-count="7"] { + .hero-image { + animation: image-switch-animation 35s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + &:nth-child(6) { animation-delay: 25s; } + &:nth-child(7) { animation-delay: 30s; } + } } - &:nth-child(3) { - animation-delay: 10s; + &[data-hero-image-count="8"] { + .hero-image { + animation: image-switch-animation 40s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + &:nth-child(6) { animation-delay: 25s; } + &:nth-child(7) { animation-delay: 30s; } + &:nth-child(8) { animation-delay: 35s; } + } } - &:nth-child(4) { - animation-delay: 15s; + &[data-hero-image-count="9"] { + .hero-image { + animation: image-switch-animation 45s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + &:nth-child(6) { animation-delay: 25s; } + &:nth-child(7) { animation-delay: 30s; } + &:nth-child(8) { animation-delay: 35s; } + &:nth-child(9) { animation-delay: 40s; } + } } - &:nth-child(5) { - animation-delay: 20s; + &[data-hero-image-count="10"] { + .hero-image { + animation: image-switch-animation 50s infinite; + &:nth-child(1) { animation-delay: 0s; } + &:nth-child(2) { animation-delay: 5s; } + &:nth-child(3) { animation-delay: 10s; } + &:nth-child(4) { animation-delay: 15s; } + &:nth-child(5) { animation-delay: 20s; } + &:nth-child(6) { animation-delay: 25s; } + &:nth-child(7) { animation-delay: 30s; } + &:nth-child(8) { animation-delay: 35s; } + &:nth-child(9) { animation-delay: 40s; } + &:nth-child(10) { animation-delay: 45s; } + } } } diff --git a/app/app/controllers/admin/media_page_settings/media_hero_images_controller.rb b/app/app/controllers/admin/media_page_settings/media_hero_images_controller.rb new file mode 100644 index 0000000..e052aa0 --- /dev/null +++ b/app/app/controllers/admin/media_page_settings/media_hero_images_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Admin + module MediaPageSettings + class MediaHeroImagesController < Admin::ApplicationController + def new + @media_hero_image = MediaHeroImage.new + end + + def edit + @media_hero_image = MediaHeroImage.find(params[:id]) + end + + def create + @media_hero_image = MediaHeroImage.new(media_hero_image_params) + + if @media_hero_image.save + redirect_to admin_media_page_setting_path + else + render :new + end + end + + def update + @media_hero_image = MediaHeroImage.find(params[:id]) + + if @media_hero_image.update(media_hero_image_params) + redirect_to admin_media_page_setting_path + else + render :edit + end + end + + def destroy + @media_hero_image = MediaHeroImage.find(params[:id]) + + @media_hero_image.destroy + + redirect_to admin_media_page_setting_path + end + + private + + def media_hero_image_params + params.require(:media_hero_image).permit(:image) + end + end + end +end diff --git a/app/app/controllers/admin/media_page_settings_controller.rb b/app/app/controllers/admin/media_page_settings_controller.rb new file mode 100644 index 0000000..f1a43fa --- /dev/null +++ b/app/app/controllers/admin/media_page_settings_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Admin + class MediaPageSettingsController < ApplicationController + def show + @media_hero_images = MediaHeroImage.sort_order_ordered + end + end +end diff --git a/app/app/controllers/media/homes_controller.rb b/app/app/controllers/media/homes_controller.rb index 3f36e99..3202e7c 100644 --- a/app/app/controllers/media/homes_controller.rb +++ b/app/app/controllers/media/homes_controller.rb @@ -5,6 +5,8 @@ class HomesController < ApplicationController NOTICES_LIMIT = 3 def show + @media_hero_images = MediaHeroImage.sort_order_ordered + @notices = Notice.only_published .published_at_ordered .limit(NOTICES_LIMIT) diff --git a/app/app/models/media_hero_image.rb b/app/app/models/media_hero_image.rb new file mode 100644 index 0000000..e82535d --- /dev/null +++ b/app/app/models/media_hero_image.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class MediaHeroImage < ApplicationRecord + include UniversalId + + setup_universal_id('mhi') + has_one_attached(:image) + + validates :sort_order, presence: true, uniqueness: true, numericality: { only_integer: true, greater_than: 0 } + + before_validation :set_sort_order, on: :create + + scope :sort_order_ordered, -> { order(:sort_order) } + + private + + def set_sort_order + return if sort_order.present? + + self.sort_order = MediaHeroImage.maximum(:sort_order).to_i + 1 + end +end diff --git a/app/app/views/admin/game_fields/_form.html.haml b/app/app/views/admin/game_fields/_form.html.haml index f4c1423..0a9519f 100644 --- a/app/app/views/admin/game_fields/_form.html.haml +++ b/app/app/views/admin/game_fields/_form.html.haml @@ -14,7 +14,7 @@ .admin-field__label = GameField.human_attribute_name(:name) .admin-field__value - = form.text_field :name, class: 'admin-form-text_field', required: true + = form.text_field :name, class: 'admin-form-text_field is-long', required: true .admin-fields__field .admin-field .admin-field__label diff --git a/app/app/views/admin/media_page_settings/media_hero_images/_form.html.haml b/app/app/views/admin/media_page_settings/media_hero_images/_form.html.haml new file mode 100644 index 0000000..2e3a356 --- /dev/null +++ b/app/app/views/admin/media_page_settings/media_hero_images/_form.html.haml @@ -0,0 +1,23 @@ += form_with model: media_hero_image, url:, local: true do |form| + .admin-fields + .admin-fields__field + .admin-field + .admin-field__label + = MediaHeroImage.human_attribute_name(:image) + .admin-field__value + .admin-form-file_field__wrapper + = form.file_field :image, class: 'admin-form-file_field__field', accept: 'image/*', required: true + = image_tag media_hero_image.image.url, class: 'admin-field-value-image' if media_hero_image.image.attached? + - if media_hero_image.errors.any? + .admin-fields__field + .admin-form-errors + %ul.admin-form-error-list + - media_hero_image.errors.full_messages.each do |message| + %li.admin-form-error-list__item + = message + .admin-fields__field + .admin-form-action_buttons + .admin-form-action_buttons__action + = form.submit t('view.admin.button.save'), class: 'admin-form-action_button' + .admin-form-action_buttons__action + = link_to t('view.admin.button.cancel'), cancel_path, class: 'admin-form-action_button is-cancel' diff --git a/app/app/views/admin/media_page_settings/media_hero_images/edit.html.haml b/app/app/views/admin/media_page_settings/media_hero_images/edit.html.haml new file mode 100644 index 0000000..94cb9b2 --- /dev/null +++ b/app/app/views/admin/media_page_settings/media_hero_images/edit.html.haml @@ -0,0 +1,17 @@ +:ruby + add_breadcrumb t('view.admin.breadcrumbs.media_page_settings.show'), admin_media_page_setting_path + add_breadcrumb t('view.admin.breadcrumbs.media_page_settings.media_hero_images.edit') + +.admin-content + .admin-content__title + .admin-title + .admin-title__image + = image_tag 'icon-setting.png', class: 'admin-title-image' + .admin-title__text + %h1.admin-title_text + ヒーロー画像 + + .admin-content__fields + = render 'form', media_hero_image: @media_hero_image, + url: admin_media_page_setting_media_hero_image_path(@media_hero_image), + cancel_path: admin_media_page_setting_path diff --git a/app/app/views/admin/media_page_settings/media_hero_images/new.html.haml b/app/app/views/admin/media_page_settings/media_hero_images/new.html.haml new file mode 100644 index 0000000..32b71f9 --- /dev/null +++ b/app/app/views/admin/media_page_settings/media_hero_images/new.html.haml @@ -0,0 +1,17 @@ +:ruby + add_breadcrumb t('view.admin.breadcrumbs.media_page_settings.show'), admin_media_page_setting_path + add_breadcrumb t('view.admin.breadcrumbs.media_page_settings.media_hero_images.new') + +.admin-content + .admin-content__title + .admin-title + .admin-title__image + = image_tag 'icon-setting.png', class: 'admin-title-image' + .admin-title__text + %h1.admin-title_text + ヒーロー画像 + + .admin-content__fields + = render 'form', media_hero_image: @media_hero_image, + url: admin_media_page_setting_media_hero_images_path, + cancel_path: admin_media_page_setting_path diff --git a/app/app/views/admin/media_page_settings/show.html.haml b/app/app/views/admin/media_page_settings/show.html.haml new file mode 100644 index 0000000..ebaa423 --- /dev/null +++ b/app/app/views/admin/media_page_settings/show.html.haml @@ -0,0 +1,55 @@ +:ruby + add_breadcrumb t('view.admin.breadcrumbs.media_page_settings.show') + +.admin-content + .admin-content__title + .admin-title + .admin-title__image + = image_tag 'icon-setting.png', class: 'admin-title-image' + .admin-title__text + %h1.admin-title_text + = t('view.admin.breadcrumbs.media_page_settings.show') + + .admin-content__main + .admin-content-main + .admin-content-main__sections + .admin-content-sections + .admin-content-sections__section + %section.admin-content-section + .admin-content-section__title + %h2.admin-content-section-title + .admin-content-section-title__text + ヒーロー画像 + .admin-content-section-title__actions + .admin-content-section-title-actions__action + = link_to t('view.admin.button.new'), + new_admin_media_page_setting_media_hero_image_path, + class: 'admin-content-section-title-action_button' + .admin-content-section__fields + %table.admin-list + %thead.admin-list__header.admin-list-header + %tr + %th.admin-list-cell.is-header + = MediaHeroImage.human_attribute_name :universal_id + %th.admin-list-cell.is-header + = MediaHeroImage.human_attribute_name :image + %th.admin-list-cell.is-header + %tbody.admin-list__body.admin-list-body + - @media_hero_images.each do |media_hero_image| + %tr + %td.admin-list-cell + .admin-list-cell + = media_hero_image.universal_id + %td.admin-list-cell + .admin-list-cell.is-image + = image_tag media_hero_image.image, class: 'admin-list-cell-image' + %td.admin-list-cell + .admin-list-cell.is-actions + = link_to t('view.admin.button.edit'), + edit_admin_media_page_setting_media_hero_image_path(media_hero_image), + class: 'common-text-link' + = button_to t('view.admin.button.destroy'), + admin_media_page_setting_media_hero_image_path(media_hero_image), + method: :delete, + data: data_for_confirm(confirm_message: t('view.admin.confirm.destroy')), + class: 'common-text-link' diff --git a/app/app/views/admin/teams/team_profiles/_form.html.haml b/app/app/views/admin/teams/team_profiles/_form.html.haml index 63a4653..2119a55 100644 --- a/app/app/views/admin/teams/team_profiles/_form.html.haml +++ b/app/app/views/admin/teams/team_profiles/_form.html.haml @@ -5,19 +5,19 @@ .admin-field__label = TeamProfile.human_attribute_name(:x_url) .admin-field__value - = form.url_field :x_url, class: 'admin-form-text_field' + = form.url_field :x_url, class: 'admin-form-text_field is-long' .admin-fields__field .admin-field .admin-field__label = TeamProfile.human_attribute_name(:instagram_url) .admin-field__value - = form.url_field :instagram_url, class: 'admin-form-text_field' + = form.url_field :instagram_url, class: 'admin-form-text_field is-long' .admin-fields__field .admin-field .admin-field__label = TeamProfile.human_attribute_name(:homepage_url) .admin-field__value - = form.url_field :homepage_url, class: 'admin-form-text_field' + = form.url_field :homepage_url, class: 'admin-form-text_field is-long' - if team_profile.errors.any? .admin-fields__field .admin-form-errors diff --git a/app/app/views/layouts/admin/_sidebar.html.haml b/app/app/views/layouts/admin/_sidebar.html.haml index e573b0c..f26adcc 100644 --- a/app/app/views/layouts/admin/_sidebar.html.haml +++ b/app/app/views/layouts/admin/_sidebar.html.haml @@ -6,6 +6,7 @@ { icon: 'icon-calendar.png', label: '試合', path: admin_game_schedules_path }, { icon: 'icon-rotate.png', label: 'シーズン', path: admin_seasons_path }, { icon: 'icon-newspaper.png', label: 'お知らせ', path: admin_notices_path }, + { icon: 'icon-setting.png', label: 'Webページ設定', path: admin_media_page_setting_path }, { icon: 'icon-administrator.png', label: '管理者', path: admin_administrators_path } ] diff --git a/app/app/views/layouts/media/_header.html.haml b/app/app/views/layouts/media/_header.html.haml index 0e7e942..13fd988 100644 --- a/app/app/views/layouts/media/_header.html.haml +++ b/app/app/views/layouts/media/_header.html.haml @@ -1,7 +1,8 @@ .application-header .application-header__logo .application-header-logo - = image_tag 'kcff-logo.png', alt: '関東学生フットボールクラブ連盟 公式サイト' + = link_to root_path do + = image_tag 'kcff-logo.png', alt: '関東学生フットボールクラブ連盟 公式サイト' .application-header__navigation .application-header-navigation .application-header-navigation__item diff --git a/app/app/views/media/homes/show.html.haml b/app/app/views/media/homes/show.html.haml index 05fff6b..d2353ed 100644 --- a/app/app/views/media/homes/show.html.haml +++ b/app/app/views/media/homes/show.html.haml @@ -1,7 +1,13 @@ :ruby - images = %w[hero-1.jpg hero-2.jpg hero-3.jpg hero-4.jpg hero-5.jpg] + # If at least one image is registered, display using those images. + # If no images are registered, display using default images. + if @media_hero_images.present? + images = @media_hero_images.map(&:image) + else + images = %w[hero-1.jpg hero-2.jpg hero-3.jpg hero-4.jpg hero-5.jpg] + end -.hero-container +.hero-container{ data: { hero_image_count: images.count } } - images.each do |image| = image_tag image, class: 'hero-image' diff --git a/app/config/locales/ja.yml b/app/config/locales/ja.yml index 04bd30f..d54e72c 100644 --- a/app/config/locales/ja.yml +++ b/app/config/locales/ja.yml @@ -76,6 +76,9 @@ ja: notice/published: published: 公開中 unpublished: 非公開 + media_hero_image: + universal_id: ID + image: 画像 date: abbr_day_names: @@ -287,6 +290,10 @@ ja: index: シーズン notices: index: お知らせ + media_page_settings: + show: Webページ設定 + media_hero_images: + new: ヒーロー画像追加 button: new: 追加する edit: 編集する diff --git a/app/config/routes/admin.rb b/app/config/routes/admin.rb index bc2f69f..bbaa184 100644 --- a/app/config/routes/admin.rb +++ b/app/config/routes/admin.rb @@ -17,4 +17,7 @@ resource :game_result, module: :game_schedules, only: %i[new create edit update destroy] end resources :notices + resource :media_page_setting, only: %i[show] do + resources :media_hero_images, module: :media_page_settings, only: %i[new create edit update destroy] + end end diff --git a/app/db/migrate/20240812052745_create_media_hero_images.rb b/app/db/migrate/20240812052745_create_media_hero_images.rb new file mode 100644 index 0000000..d04d55b --- /dev/null +++ b/app/db/migrate/20240812052745_create_media_hero_images.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateMediaHeroImages < ActiveRecord::Migration[7.2] + def change + create_table :media_hero_images, comment: 'トップページのヒーロー画像' do |t| + t.integer :sort_order, null: false, index: { unique: true }, comment: '並び順' + t.string :universal_id, null: false, index: { unique: true }, comment: 'ユニバーサルID' + + t.timestamps + end + end +end diff --git a/app/db/schema.rb b/app/db/schema.rb index 956e612..bd94790 100644 --- a/app/db/schema.rb +++ b/app/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_08_10_154237) do +ActiveRecord::Schema[7.2].define(version: 2024_08_12_052745) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -93,6 +93,15 @@ t.index ["visitor_team_id"], name: "index_game_schedules_on_visitor_team_id" end + create_table "media_hero_images", comment: "トップページのヒーロー画像", force: :cascade do |t| + t.integer "sort_order", null: false, comment: "並び順" + t.string "universal_id", null: false, comment: "ユニバーサルID" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["sort_order"], name: "index_media_hero_images_on_sort_order", unique: true + t.index ["universal_id"], name: "index_media_hero_images_on_universal_id", unique: true + end + create_table "notices", force: :cascade do |t| t.string "title", null: false, comment: "タイトル" t.text "content", null: false, comment: "内容" diff --git a/app/spec/factories/media_hero_images.rb b/app/spec/factories/media_hero_images.rb new file mode 100644 index 0000000..ac8ce8f --- /dev/null +++ b/app/spec/factories/media_hero_images.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'open-uri' + +FactoryBot.define do + factory :media_hero_image do + sequence(:sort_order) { |n| n } + + after :build do |media_hero_image| + image = URI.parse('https://picsum.photos/600/200').open + + media_hero_image.image.attach( + io: image, + filename: "media_hero_image_#{media_hero_image.sort_order}.jpg", + content_type: 'image/jpg' + ) + end + end +end