From b5fbc1e2eb46866a8cc5a6505c3371a77ea1bc97 Mon Sep 17 00:00:00 2001 From: Earlopain Date: Sat, 15 Oct 2022 21:40:40 +0200 Subject: [PATCH] [Home] Add dynamic mascots --- app/controllers/mascots_controller.rb | 43 ++++++++++ app/decorators/mod_action_decorator.rb | 8 ++ app/javascript/src/javascripts/mascots.js | 37 +++++---- app/logical/storage_manager.rb | 19 +++++ app/models/mascot.rb | 83 +++++++++++++++++++ app/models/mod_action.rb | 3 + app/views/mascots/_form.html.erb | 10 +++ app/views/mascots/_secondary_links.html.erb | 8 ++ app/views/mascots/edit.html.erb | 12 +++ app/views/mascots/index.html.erb | 47 +++++++++++ app/views/mascots/new.html.erb | 12 +++ app/views/static/home.html.erb | 2 +- app/views/static/site_map.html.erb | 1 + config/danbooru_default_config.rb | 13 --- config/routes.rb | 1 + db/migrate/20221014085948_add_mascot_table.rb | 15 ++++ db/structure.sql | 78 ++++++++++++++++- 17 files changed, 359 insertions(+), 33 deletions(-) create mode 100644 app/controllers/mascots_controller.rb create mode 100644 app/models/mascot.rb create mode 100644 app/views/mascots/_form.html.erb create mode 100644 app/views/mascots/_secondary_links.html.erb create mode 100644 app/views/mascots/edit.html.erb create mode 100644 app/views/mascots/index.html.erb create mode 100644 app/views/mascots/new.html.erb create mode 100644 db/migrate/20221014085948_add_mascot_table.rb diff --git a/app/controllers/mascots_controller.rb b/app/controllers/mascots_controller.rb new file mode 100644 index 000000000..714318438 --- /dev/null +++ b/app/controllers/mascots_controller.rb @@ -0,0 +1,43 @@ +class MascotsController < ApplicationController + respond_to :html, :json + before_action :admin_only, except: [:index] + + def index + @mascots = Mascot.search(search_params).paginate(params[:page], limit: 75) + respond_with(@mascots) + end + + def new + @mascot = Mascot.new + end + + def create + @mascot = Mascot.create(mascot_params.merge(creator: CurrentUser.user)) + ModAction.log(:mascot_create, { id: @mascot.id }) if @mascot.valid? + respond_with(@mascot, location: mascots_path) + end + + def edit + @mascot = Mascot.find(params[:id]) + end + + def update + @mascot = Mascot.find(params[:id]) + @mascot.update(mascot_params) + ModAction.log(:mascot_update, { id: @mascot.id }) if @mascot.valid? + respond_with(@mascot, location: mascots_path) + end + + def destroy + @mascot = Mascot.find(params[:id]) + @mascot.destroy + ModAction.log(:mascot_delete, { id: @mascot.id }) + respond_with(@mascot) + end + + private + + def mascot_params + params.fetch(:mascot, {}).permit(%i[mascot_file display_name background_color artist_url artist_name active]) + end +end diff --git a/app/decorators/mod_action_decorator.rb b/app/decorators/mod_action_decorator.rb index 4b3a9bea6..33ed214cb 100644 --- a/app/decorators/mod_action_decorator.rb +++ b/app/decorators/mod_action_decorator.rb @@ -292,6 +292,14 @@ class ModActionDecorator < ApplicationDecorator when "wiki_page_rename" "Renamed wiki page ([[#{vals['old_title']}]] → [[#{vals['new_title']}]])" + ### Mascots ### + when "mascot_create" + "Created mascot ##{vals['id']}" + when "mascot_update" + "Updated mascot ##{vals['id']}" + when "mascot_delete" + "Deleted mascot ##{vals['id']}" + when "bulk_revert" "Processed bulk revert for #{vals['constraints']} by #{user}" diff --git a/app/javascript/src/javascripts/mascots.js b/app/javascript/src/javascripts/mascots.js index fa248327c..b56dcee10 100644 --- a/app/javascript/src/javascripts/mascots.js +++ b/app/javascript/src/javascripts/mascots.js @@ -4,26 +4,24 @@ const Mascots = { current: 0 }; -function showMascot(cur) { - const mascots = window.mascots; +function showMascot(mascot) { + $('body').css("background-image", "url(" + mascot.background_url + ")"); + $('body').css("background-color", mascot.background_color); + $('.mascotbox').css("background-image", "url(" + mascot.background_url + ")"); + $('.mascotbox').css("background-color", mascot.background_color); - $('body').css("background-image", "url(" + mascots[cur][0] + ")"); - $('body').css("background-color", mascots[cur][1]); - $('.mascotbox').css("background-image", "url(" + mascots[cur][0] + ")"); - $('.mascotbox').css("background-color", mascots[cur][1]); - - if (mascots[cur][2]) - $('#mascot_artist').html("Mascot by " + mascots[cur][2]); - else - $('#mascot_artist').html(" "); + const artistLink = $("").text("Mascot by ").append($("").text(mascot.artist_name).attr("href", mascot.artist_url)); + $("#mascot_artist").empty().append(artistLink); } function changeMascot() { const mascots = window.mascots; - Mascots.current += 1; - Mascots.current = Mascots.current % mascots.length; - showMascot(Mascots.current); + const availableMascotIds = Object.keys(mascots); + const currentMascotIndex = availableMascotIds.indexOf(Mascots.current); + + Mascots.current = availableMascotIds[(currentMascotIndex + 1) % availableMascotIds.length]; + showMascot(mascots[Mascots.current]); LS.put('mascot', Mascots.current); } @@ -31,10 +29,13 @@ function changeMascot() { function initMascots() { $('#change-mascot').on('click', changeMascot); const mascots = window.mascots; - Mascots.current = parseInt(LS.get("mascot")); - if (isNaN(Mascots.current) || Mascots.current < 0 || Mascots.current >= mascots.length) - Mascots.current = Math.floor(Math.random() * mascots.length); - showMascot(Mascots.current); + Mascots.current = LS.get("mascot"); + if (!mascots[Mascots.current]) { + const availableMascotIds = Object.keys(mascots); + const mascotIndex = Math.floor(Math.random() * availableMascotIds.length); + Mascots.current = availableMascotIds[mascotIndex]; + } + showMascot(mascots[Mascots.current]); } $(function () { diff --git a/app/logical/storage_manager.rb b/app/logical/storage_manager.rb index 0b146b871..0b0ee375b 100644 --- a/app/logical/storage_manager.rb +++ b/app/logical/storage_manager.rb @@ -3,6 +3,7 @@ class StorageManager DEFAULT_BASE_DIR = "#{Rails.root}/public/data" IMAGE_TYPES = %i[preview large crop original] + MASCOT_PREFIX = "mascots" attr_reader :base_url, :base_dir, :hierarchical, :large_image_prefix, :protected_prefix, :base_path, :replacement_prefix @@ -185,6 +186,24 @@ class StorageManager "#{base_dir}/#{replacement_prefix}/#{subdir}#{file}" end + def store_mascot(io, mascot) + store(io, mascot_path(mascot.md5, mascot.file_ext)) + end + + def mascot_path(md5, file_ext) + file = "#{md5}.#{file_ext}" + "#{base_dir}/#{MASCOT_PREFIX}/#{file}" + end + + def mascot_url(mascot) + file = "#{mascot.md5}.#{mascot.file_ext}" + "#{base_url}#{base_path}/#{MASCOT_PREFIX}/#{file}" + end + + def delete_mascot(md5, file_ext) + delete(mascot_path(md5, file_ext)) + end + def subdir_for(md5) hierarchical ? "#{md5[0..1]}/#{md5[2..3]}/" : "" end diff --git a/app/models/mascot.rb b/app/models/mascot.rb new file mode 100644 index 000000000..cd9ef2eb1 --- /dev/null +++ b/app/models/mascot.rb @@ -0,0 +1,83 @@ +class Mascot < ApplicationRecord + belongs_to_creator + + attr_accessor :mascot_file + + validates :display_name, :background_color, :artist_url, :artist_name, presence: true + validates :artist_url, format: { with: %r{\Ahttps?://}, message: "must start with http:// or https://" } + validates :mascot_file, presence: true, on: :create + validate :set_file_properties + validates :md5, uniqueness: true + validate if: :mascot_file do |mascot| + max_file_sizes = { "jpg" => 500.kilobytes, "png" => 500.kilobytes } + FileValidator.new(mascot, mascot_file.path).validate(max_file_sizes: max_file_sizes, max_width: 1_000, max_height: 1_000) + end + + after_commit :invalidate_cache + after_save_commit :write_storage_file + after_destroy_commit :remove_storage_file + + def set_file_properties + return if mascot_file.blank? + + self.file_ext = file_header_to_file_ext(mascot_file.path) + self.md5 = Digest::MD5.file(mascot_file.path).hexdigest + end + + def write_storage_file + return if mascot_file.blank? + + Danbooru.config.storage_manager.delete_mascot(md5_previously_was, file_ext_previously_was) + Danbooru.config.storage_manager.store_mascot(mascot_file, self) + end + + def self.active_for_browser + Cache.get("active_mascots", 1.day) do + mascots = Mascot.where(active: true).map do |mascot| + mascot.slice(:id, :background_color, :artist_url, :artist_name).merge(background_url: mascot.url_path) + end + mascots.index_by { |mascot| mascot["id"] } + end + end + + def invalidate_cache + Cache.delete("active_mascots") + end + + def remove_storage_file + Danbooru.config.storage_manager.delete_mascot(md5, file_ext) + end + + def url_path + Danbooru.config.storage_manager.mascot_url(self) + end + + def file_path + Danbooru.config.storage_manager.mascot_path(self) + end + + concerning :ValidationMethods do + def dimensions + @dimensions ||= calculate_dimensions(mascot_file.path) + end + + def image_width + dimensions[0] + end + + def image_height + dimensions[1] + end + + def file_size + @file_size ||= Danbooru.config.storage_manager.open(mascot_file.path).size + end + end + + def self.search(params) + q = super + q.order("lower(artist_name)") + end + + include FileMethods +end diff --git a/app/models/mod_action.rb b/app/models/mod_action.rb index 22189f181..83ef3b472 100644 --- a/app/models/mod_action.rb +++ b/app/models/mod_action.rb @@ -39,6 +39,9 @@ class ModAction < ApplicationRecord :help_update, :ip_ban_create, :ip_ban_delete, + :mascot_create, + :mascot_update, + :mascot_delete, :pool_delete, :report_reason_create, :report_reason_delete, diff --git a/app/views/mascots/_form.html.erb b/app/views/mascots/_form.html.erb new file mode 100644 index 000000000..686e1608f --- /dev/null +++ b/app/views/mascots/_form.html.erb @@ -0,0 +1,10 @@ +<%= custom_form_for(mascot) do |f| %> + <%= error_messages_for "mascot" %> + <%= f.input :mascot_file, as: :file, input_html: { accept: "image/png,image/jpeg,.png,.jpg,.jpeg" } %> + <%= f.input :display_name %> + <%= f.input :background_color %> + <%= f.input :artist_url %> + <%= f.input :artist_name %> + <%= f.input :active %> + <%= f.submit %> +<% end %> diff --git a/app/views/mascots/_secondary_links.html.erb b/app/views/mascots/_secondary_links.html.erb new file mode 100644 index 000000000..f2cdb7186 --- /dev/null +++ b/app/views/mascots/_secondary_links.html.erb @@ -0,0 +1,8 @@ +<% content_for(:secondary_links) do %> + + <%= subnav_link_to "Listing", mascots_path %> + <% if CurrentUser.user.is_admin? %> + <%= subnav_link_to "New", new_mascot_path %> + <% end %> + +<% end %> diff --git a/app/views/mascots/edit.html.erb b/app/views/mascots/edit.html.erb new file mode 100644 index 000000000..9929ffe34 --- /dev/null +++ b/app/views/mascots/edit.html.erb @@ -0,0 +1,12 @@ +
+
+

Edit Mascot

+ <%= render "form", mascot: @mascot %> +
+
+ +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + Edit Mascot +<% end %> diff --git a/app/views/mascots/index.html.erb b/app/views/mascots/index.html.erb new file mode 100644 index 000000000..43a9aa8eb --- /dev/null +++ b/app/views/mascots/index.html.erb @@ -0,0 +1,47 @@ +
+
+

Mascots

+ + + + + + + + + + + <% if CurrentUser.user.is_admin? %> + + <% end %> + + + + <% @mascots.each do |mascot| %> + + + + + + + + <% if CurrentUser.user.is_admin? %> + + <% end %> + + <% end %> + +
NameBackground ColorArtist NameArtist URLActiveCreated
<%= link_to mascot.display_name, mascot.url_path %><%= mascot.background_color %><%= mascot.artist_name %><%= mascot.artist_url %><%= mascot.active %><%= compact_time mascot.created_at %> + <%= link_to "Edit", edit_mascot_path(mascot) %> + | <%= link_to "Delete", mascot_path(mascot), method: :delete, data: { confirm: "Are you sure you want to delete this mascot?" } %> +
+ + <%= numbered_paginator(@mascots) %> +
+
+ +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + Mascots +<% end %> diff --git a/app/views/mascots/new.html.erb b/app/views/mascots/new.html.erb new file mode 100644 index 000000000..f0594aece --- /dev/null +++ b/app/views/mascots/new.html.erb @@ -0,0 +1,12 @@ +
+
+

New Mascot

+ <%= render "form", mascot: @mascot %> +
+
+ +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + New Mascot +<% end %> diff --git a/app/views/static/home.html.erb b/app/views/static/home.html.erb index e5514250c..e4e32e00a 100644 --- a/app/views/static/home.html.erb +++ b/app/views/static/home.html.erb @@ -87,7 +87,7 @@
<%= javascript_tag nonce: true do -%> - var mascots = <%= Danbooru.config.mascots.to_json.html_safe %>; + var mascots = <%= Mascot.active_for_browser.to_json.html_safe %>; <% end -%>