From dbe2f9e845c01d56e5c967bea5b2d19f0393be2f Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 23 Apr 2024 17:20:09 +0300 Subject: [PATCH 01/13] Move generator code to it's own class --- app/lib/icon_generator.rb | 35 +++++++++++++++++++++++++++++++++++ lib/tasks/branding.rake | 24 +++++------------------- 2 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 app/lib/icon_generator.rb diff --git a/app/lib/icon_generator.rb b/app/lib/icon_generator.rb new file mode 100644 index 0000000000000..c6d4873a8caba --- /dev/null +++ b/app/lib/icon_generator.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module IconGenerator + extend self + + def generate_favicons(source_path, output_dest, sizes) + favicons = generate_icons(source_path, output_dest, sizes, 'favicon') + + convert.run(input: favicons, output: Rails.public_path.join('favicon.ico')) + end + + def generate_app_icons(source_path, output_dest, sizes, prefix) + generate_icons(source_path, output_dest, sizes, prefix) + end + + private + + def generate_icons(source_path, output_dest, sizes, prefix) + icon_paths = [] + sizes.each do |size| + output_path = output_dest.join("#{prefix}-#{size}x#{size}.png") + icon_paths << output_path + rsvg_convert.run(size: size, input: source_path, output: output_path) + end + icon_paths + end + + def rsvg_convert + @rsvg_convert ||= Terrapin::CommandLine.new('rsvg-convert', '-w :size -h :size --keep-aspect-ratio :input -o :output') + end + + def convert + @convert ||= Terrapin::CommandLine.new('convert', ':input :output', environment: { 'MAGICK_CONFIGURE_PATH' => nil }) + end +end diff --git a/lib/tasks/branding.rake b/lib/tasks/branding.rake index 608fb3af9c418..7f3d4d32728f9 100644 --- a/lib/tasks/branding.rake +++ b/lib/tasks/branding.rake @@ -41,30 +41,16 @@ namespace :branding do app_icon_source = Rails.root.join('app', 'javascript', 'images', 'app-icon.svg') output_dest = Rails.root.join('app', 'javascript', 'icons') - rsvg_convert = Terrapin::CommandLine.new('rsvg-convert', '-w :size -h :size --keep-aspect-ratio :input -o :output') - convert = Terrapin::CommandLine.new('convert', ':input :output', environment: { 'MAGICK_CONFIGURE_PATH' => nil }) - favicon_sizes = [16, 32, 48] apple_icon_sizes = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024] android_icon_sizes = [36, 48, 72, 96, 144, 192, 256, 384, 512] - favicons = [] - - favicon_sizes.each do |size| - output_path = output_dest.join("favicon-#{size}x#{size}.png") - favicons << output_path - rsvg_convert.run(size: size, input: favicon_source, output: output_path) - end - - convert.run(input: favicons, output: Rails.public_path.join('favicon.ico')) + # Generate favicons + IconGenerator.generate_favicons(favicon_source, output_dest, favicon_sizes) - apple_icon_sizes.each do |size| - rsvg_convert.run(size: size, input: app_icon_source, output: output_dest.join("apple-touch-icon-#{size}x#{size}.png")) - end - - android_icon_sizes.each do |size| - rsvg_convert.run(size: size, input: app_icon_source, output: output_dest.join("android-chrome-#{size}x#{size}.png")) - end + # Generate app icons + IconGenerator.generate_app_icons(app_icon_source, output_dest, apple_icon_sizes, 'apple-touch-icon') + IconGenerator.generate_app_icons(app_icon_source, output_dest, android_icon_sizes, 'android-chrome') end desc 'Generate badge icon from SVG source files' From 6379a0eb559db1b21b4a402f95a5b95a6b72cd93 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 23 Apr 2024 17:43:37 +0300 Subject: [PATCH 02/13] Add input fields for favicon and app_icon --- app/views/admin/settings/appearance/show.html.haml | 12 ++++++++++++ config/locales/simple_form.en-GB.yml | 2 ++ config/locales/simple_form.en.yml | 2 ++ 3 files changed, 16 insertions(+) diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index 3ef4920cd50d5..c6e96ec11898a 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -23,6 +23,18 @@ input_html: { rows: 8 }, wrapper: :with_block_label + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :favicon, + as: :file, + wrapper: :with_block_label + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :app_icon, + as: :file, + wrapper: :with_block_label + .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :mascot, diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index e5c9b1b2b7f2b..85689f95d3ce2 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -77,11 +77,13 @@ en-GB: warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets + app_icon: Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible. custom_css: You can apply custom styles on the web version of Mastodon. + favicon: Overrides the default Mastodon favicon with a custom icon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 4ba6e88f41818..d06ee60e8a2a7 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -77,11 +77,13 @@ en: warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets + app_icon: Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. custom_css: You can apply custom styles on the web version of Mastodon. + favicon: Overrides the default Mastodon favicon with a custom icon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. From b417bfcf3c407536f98a9cf49938f39405951f9c Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Fri, 26 Apr 2024 10:15:20 +0300 Subject: [PATCH 03/13] Allow configuration of Favicon --- app/helpers/application_helper.rb | 7 +++++++ app/models/form/admin_settings.rb | 2 ++ app/models/site_upload.rb | 5 +++++ app/views/layouts/application.html.haml | 4 ++-- spec/helpers/application_helper_spec.rb | 24 ++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 668afe7fde13d..101612e25c38a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -232,6 +232,13 @@ def prerender_custom_emojis(html, custom_emojis, other_options = {}) EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end + def site_favicon_path(size = :'32') + favicon = SiteUpload.find_by(var: 'favicon') + return nil unless favicon + + favicon.file.url(size) + end + private def storage_host_var diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index cb37a522174d4..20b15ab7a3fa2 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -37,6 +37,7 @@ class Form::AdminSettings status_page_url captcha_enabled authorized_fetch + favicon ).freeze INTEGER_KEYS = %i( @@ -63,6 +64,7 @@ class Form::AdminSettings UPLOAD_KEYS = %i( thumbnail mascot + favicon ).freeze OVERRIDEN_SETTINGS = { diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 03d472cdb2e73..17d7a04850f2b 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -20,6 +20,11 @@ class SiteUpload < ApplicationRecord include Attachmentable STYLES = { + favicon: { + '16': '16x16#', + '32': '32x32#', + '48': '48x48#', + }.freeze, thumbnail: { '@1x': { format: 'png', diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 9d7669d685124..213d109125406 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,10 +11,10 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ - %link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/ + %link{ rel: 'icon', href: site_favicon_path || '/favicon.ico', type: 'image/x-icon' }/ - %w(16 32 48).each do |size| - %link{ rel: 'icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_favicon_path(size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9330eb0dae110..adcd41ebc9473 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -285,4 +285,28 @@ def current_theme = 'default' end end end + + describe '#site_favicon_path' do + context 'when favicon exists' do + let!(:favicon) { Fabricate(:site_upload, var: 'favicon') } + + it 'returns the URL of the favicon' do + expect(helper.site_favicon_path).to eq(favicon.file.url('32')) + end + + it 'returns the URL of the favicon with size parameter' do + expect(helper.site_favicon_path(16)).to eq(favicon.file.url('16')) + end + end + + context 'when favicon does not exist' do + it 'returns nil' do + expect(helper.site_favicon_path).to be_nil + end + + it 'returns nil with size parameter' do + expect(helper.site_favicon_path(16)).to be_nil + end + end + end end From c49b77f7504261251f664cde0a4b9d5adebc3bb6 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 15:18:04 +0300 Subject: [PATCH 04/13] Refactor how we get icon sizes --- app/helpers/application_helper.rb | 8 ++++---- app/models/site_upload.rb | 7 ++----- app/views/layouts/application.html.haml | 6 +++--- spec/helpers/application_helper_spec.rb | 10 +++++----- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 101612e25c38a..1b1e682005b7b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -232,11 +232,11 @@ def prerender_custom_emojis(html, custom_emojis, other_options = {}) EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end - def site_favicon_path(size = :'32') - favicon = SiteUpload.find_by(var: 'favicon') - return nil unless favicon + def site_icon_path(type, size = :'32') + icon = SiteUpload.find_by(var: type) + return nil unless icon - favicon.file.url(size) + icon.file.url(size) end private diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 17d7a04850f2b..93939ca40dc05 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -19,12 +19,9 @@ class SiteUpload < ApplicationRecord include Attachmentable + FAVICON_SIZES = [16, 32, 48].freeze STYLES = { - favicon: { - '16': '16x16#', - '32': '32x32#', - '48': '48x48#', - }.freeze, + favicon: FAVICON_SIZES.each_with_object({}) { |size, hash| hash[size.to_s.to_sym] = "#{size}x#{size}#" }.freeze, thumbnail: { '@1x': { format: 'png', diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 213d109125406..9665d5c0fd8e0 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,10 +11,10 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ - %link{ rel: 'icon', href: site_favicon_path || '/favicon.ico', type: 'image/x-icon' }/ + %link{ rel: 'icon', href: site_icon_path('favicon') || '/favicon.ico', type: 'image/x-icon' }/ - - %w(16 32 48).each do |size| - %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_favicon_path(size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + - SiteUpload::FAVICON_SIZES.each do |size| + %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_icon_path('favicon', size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index adcd41ebc9473..9a1b03b61e637 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -286,26 +286,26 @@ def current_theme = 'default' end end - describe '#site_favicon_path' do + describe '#site_icon_path' do context 'when favicon exists' do let!(:favicon) { Fabricate(:site_upload, var: 'favicon') } it 'returns the URL of the favicon' do - expect(helper.site_favicon_path).to eq(favicon.file.url('32')) + expect(helper.site_icon_path('favicon')).to eq(favicon.file.url('32')) end it 'returns the URL of the favicon with size parameter' do - expect(helper.site_favicon_path(16)).to eq(favicon.file.url('16')) + expect(helper.site_icon_path('favicon', 16)).to eq(favicon.file.url('16')) end end context 'when favicon does not exist' do it 'returns nil' do - expect(helper.site_favicon_path).to be_nil + expect(helper.site_icon_path('favicon')).to be_nil end it 'returns nil with size parameter' do - expect(helper.site_favicon_path(16)).to be_nil + expect(helper.site_icon_path('favicon', 16)).to be_nil end end end From 945f4f848c62231f3d8efb03ac3b499fe206ec21 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 16:31:48 +0300 Subject: [PATCH 05/13] Allow configuration of app icons --- app/models/form/admin_settings.rb | 2 ++ app/models/site_upload.rb | 6 ++++++ app/serializers/manifest_serializer.rb | 20 ++++++-------------- app/views/layouts/application.html.haml | 3 +++ spec/helpers/application_helper_spec.rb | 8 ++++---- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 20b15ab7a3fa2..85b913cf80aad 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -37,6 +37,7 @@ class Form::AdminSettings status_page_url captcha_enabled authorized_fetch + app_icon favicon ).freeze @@ -64,6 +65,7 @@ class Form::AdminSettings UPLOAD_KEYS = %i( thumbnail mascot + app_icon favicon ).freeze diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 93939ca40dc05..b3926ec7ed598 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -20,7 +20,13 @@ class SiteUpload < ApplicationRecord include Attachmentable FAVICON_SIZES = [16, 32, 48].freeze + APPLE_ICON_SIZES = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024].freeze + ANDROID_ICON_SIZES = [36, 48, 72, 96, 144, 192, 256, 384, 512].freeze + + APP_ICON_SIZES = (APPLE_ICON_SIZES + ANDROID_ICON_SIZES).uniq.freeze + STYLES = { + app_icon: APP_ICON_SIZES.each_with_object({}) { |size, hash| hash[size.to_s.to_sym] = "#{size}x#{size}#" }.freeze, favicon: FAVICON_SIZES.each_with_object({}) { |size, hash| hash[size.to_s.to_sym] = "#{size}x#{size}#" }.freeze, thumbnail: { '@1x': { diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index 1c1f7d0ad534a..759490228c04b 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -1,21 +1,10 @@ # frozen_string_literal: true class ManifestSerializer < ActiveModel::Serializer + include ApplicationHelper include RoutingHelper include ActionView::Helpers::TextHelper - ICON_SIZES = %w( - 36 - 48 - 72 - 96 - 144 - 192 - 256 - 384 - 512 - ).freeze - attributes :id, :name, :short_name, :icons, :theme_color, :background_color, :display, :start_url, :scope, @@ -37,9 +26,12 @@ def short_name end def icons - ICON_SIZES.map do |size| + SiteUpload::ANDROID_ICON_SIZES.map do |size| + src = site_icon_path('app_icon', size.to_i) + src = URI.join(root_url, src).to_s if src.present? + { - src: frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), + src: src || frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), sizes: "#{size}x#{size}", type: 'image/png', purpose: 'any maskable', diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 9665d5c0fd8e0..f08e375bb9980 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -16,6 +16,9 @@ - SiteUpload::FAVICON_SIZES.each do |size| %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_icon_path('favicon', size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + - SiteUpload::APPLE_ICON_SIZES.each do |size| + %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: site_icon_path('app_icon', size.to_i) || frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ + - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9a1b03b61e637..de321b0737f04 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -287,19 +287,19 @@ def current_theme = 'default' end describe '#site_icon_path' do - context 'when favicon exists' do + context 'when an icon exists' do let!(:favicon) { Fabricate(:site_upload, var: 'favicon') } - it 'returns the URL of the favicon' do + it 'returns the URL of the icon' do expect(helper.site_icon_path('favicon')).to eq(favicon.file.url('32')) end - it 'returns the URL of the favicon with size parameter' do + it 'returns the URL of the icon with size parameter' do expect(helper.site_icon_path('favicon', 16)).to eq(favicon.file.url('16')) end end - context 'when favicon does not exist' do + context 'when an icon does not exist' do it 'returns nil' do expect(helper.site_icon_path('favicon')).to be_nil end From d9fe5ed67c20bb80f308bf68c86f88b42901e5cb Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 16:32:40 +0300 Subject: [PATCH 06/13] Remove duplication --- app/views/layouts/application.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index f08e375bb9980..56857b2b635c1 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -19,9 +19,6 @@ - SiteUpload::APPLE_ICON_SIZES.each do |size| %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: site_icon_path('app_icon', size.to_i) || frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ - - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| - %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ - %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/ = theme_color_tags current_theme From 8d55a81fc5b06c0a607d2105f58647bf13dd35ed Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 16:33:14 +0300 Subject: [PATCH 07/13] Implement removing favicon and app icon uploads --- app/views/admin/settings/appearance/show.html.haml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index c6e96ec11898a..b67af4538d86f 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -29,12 +29,26 @@ as: :file, wrapper: :with_block_label + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.favicon.persisted? + = image_tag @admin_settings.favicon.file.url(:'48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :app_icon, as: :file, wrapper: :with_block_label + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.app_icon.persisted? + = image_tag @admin_settings.app_icon.file.url(:'48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :mascot, From a09cdef36eee838ce9e83cb1d3802ed75620b220 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 21:21:17 +0300 Subject: [PATCH 08/13] Some cleanup --- app/views/admin/settings/appearance/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index b67af4538d86f..2aa22727661ed 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -31,7 +31,7 @@ .fields-row__column.fields-row__column-6.fields-group - if @admin_settings.favicon.persisted? - = image_tag @admin_settings.favicon.file.url(:'48'), class: 'fields-group__thumbnail' + = image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail' = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do = fa_icon 'trash fw' = t('admin.site_uploads.delete') @@ -44,7 +44,7 @@ .fields-row__column.fields-row__column-6.fields-group - if @admin_settings.app_icon.persisted? - = image_tag @admin_settings.app_icon.file.url(:'48'), class: 'fields-group__thumbnail' + = image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail' = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do = fa_icon 'trash fw' = t('admin.site_uploads.delete') From ea5cbea9c97c8138635b630bfc72497d4ba6aa5f Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 21:32:36 +0300 Subject: [PATCH 09/13] Limit favicon and app icon uploads to a few image formats --- app/views/admin/settings/appearance/show.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index 2aa22727661ed..4125fe6c50da3 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -27,6 +27,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :favicon, as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group @@ -40,6 +41,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :app_icon, as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group From d7b8ebf5bf652227010c8523037a2f02e7f3cdc9 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 21:39:01 +0300 Subject: [PATCH 10/13] Update labels for favicon and app icon uploads --- config/locales/simple_form.en-GB.yml | 4 ++-- config/locales/simple_form.en.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index 85689f95d3ce2..679b838663071 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -77,13 +77,13 @@ en-GB: warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets - app_icon: Overrides the default app icon on mobile devices with a custom icon. + app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible. custom_css: You can apply custom styles on the web version of Mastodon. - favicon: Overrides the default Mastodon favicon with a custom icon. + favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index d06ee60e8a2a7..b5b60ea2972bc 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -77,13 +77,13 @@ en: warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets - app_icon: Overrides the default app icon on mobile devices with a custom icon. + app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon. backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. custom_css: You can apply custom styles on the web version of Mastodon. - favicon: Overrides the default Mastodon favicon with a custom icon. + favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. From 11bb58a4e55906cf244bbcc0110637e5c42a3153 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 21:52:45 +0300 Subject: [PATCH 11/13] Revert "Move generator code to it's own class" This reverts commit dbe2f9e845c01d56e5c967bea5b2d19f0393be2f. Reason: https://github.com/mastodon/mastodon/pull/30040/commits/dbe2f9e845c01d56e5c967bea5b2d19f0393be2f#r1585347137 --- app/lib/icon_generator.rb | 35 ----------------------------------- lib/tasks/branding.rake | 24 +++++++++++++++++++----- 2 files changed, 19 insertions(+), 40 deletions(-) delete mode 100644 app/lib/icon_generator.rb diff --git a/app/lib/icon_generator.rb b/app/lib/icon_generator.rb deleted file mode 100644 index c6d4873a8caba..0000000000000 --- a/app/lib/icon_generator.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module IconGenerator - extend self - - def generate_favicons(source_path, output_dest, sizes) - favicons = generate_icons(source_path, output_dest, sizes, 'favicon') - - convert.run(input: favicons, output: Rails.public_path.join('favicon.ico')) - end - - def generate_app_icons(source_path, output_dest, sizes, prefix) - generate_icons(source_path, output_dest, sizes, prefix) - end - - private - - def generate_icons(source_path, output_dest, sizes, prefix) - icon_paths = [] - sizes.each do |size| - output_path = output_dest.join("#{prefix}-#{size}x#{size}.png") - icon_paths << output_path - rsvg_convert.run(size: size, input: source_path, output: output_path) - end - icon_paths - end - - def rsvg_convert - @rsvg_convert ||= Terrapin::CommandLine.new('rsvg-convert', '-w :size -h :size --keep-aspect-ratio :input -o :output') - end - - def convert - @convert ||= Terrapin::CommandLine.new('convert', ':input :output', environment: { 'MAGICK_CONFIGURE_PATH' => nil }) - end -end diff --git a/lib/tasks/branding.rake b/lib/tasks/branding.rake index 7f3d4d32728f9..608fb3af9c418 100644 --- a/lib/tasks/branding.rake +++ b/lib/tasks/branding.rake @@ -41,16 +41,30 @@ namespace :branding do app_icon_source = Rails.root.join('app', 'javascript', 'images', 'app-icon.svg') output_dest = Rails.root.join('app', 'javascript', 'icons') + rsvg_convert = Terrapin::CommandLine.new('rsvg-convert', '-w :size -h :size --keep-aspect-ratio :input -o :output') + convert = Terrapin::CommandLine.new('convert', ':input :output', environment: { 'MAGICK_CONFIGURE_PATH' => nil }) + favicon_sizes = [16, 32, 48] apple_icon_sizes = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024] android_icon_sizes = [36, 48, 72, 96, 144, 192, 256, 384, 512] - # Generate favicons - IconGenerator.generate_favicons(favicon_source, output_dest, favicon_sizes) + favicons = [] + + favicon_sizes.each do |size| + output_path = output_dest.join("favicon-#{size}x#{size}.png") + favicons << output_path + rsvg_convert.run(size: size, input: favicon_source, output: output_path) + end + + convert.run(input: favicons, output: Rails.public_path.join('favicon.ico')) - # Generate app icons - IconGenerator.generate_app_icons(app_icon_source, output_dest, apple_icon_sizes, 'apple-touch-icon') - IconGenerator.generate_app_icons(app_icon_source, output_dest, android_icon_sizes, 'android-chrome') + apple_icon_sizes.each do |size| + rsvg_convert.run(size: size, input: app_icon_source, output: output_dest.join("apple-touch-icon-#{size}x#{size}.png")) + end + + android_icon_sizes.each do |size| + rsvg_convert.run(size: size, input: app_icon_source, output: output_dest.join("android-chrome-#{size}x#{size}.png")) + end end desc 'Generate badge icon from SVG source files' From 57adc74d6b91bd963b4fc7a661d549e09b67d00e Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Tue, 30 Apr 2024 21:59:38 +0300 Subject: [PATCH 12/13] Change default size for site_icon_path --- app/helpers/application_helper.rb | 2 +- spec/helpers/application_helper_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1b1e682005b7b..c630cfe6dd84a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -232,7 +232,7 @@ def prerender_custom_emojis(html, custom_emojis, other_options = {}) EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end - def site_icon_path(type, size = :'32') + def site_icon_path(type, size = '48') icon = SiteUpload.find_by(var: type) return nil unless icon diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index de321b0737f04..56501034b2ddd 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -291,7 +291,7 @@ def current_theme = 'default' let!(:favicon) { Fabricate(:site_upload, var: 'favicon') } it 'returns the URL of the icon' do - expect(helper.site_icon_path('favicon')).to eq(favicon.file.url('32')) + expect(helper.site_icon_path('favicon')).to eq(favicon.file.url('48')) end it 'returns the URL of the icon with size parameter' do From 737ddd67f2ed7c0cc5ac342d9547a85f387977e6 Mon Sep 17 00:00:00 2001 From: Fawaz Farid Date: Sat, 4 May 2024 17:29:30 +0300 Subject: [PATCH 13/13] Move favicon and app icon config to Branding --- .../admin/settings/appearance/show.html.haml | 28 ------------------- .../admin/settings/branding/show.html.haml | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index 4125fe6c50da3..3ef4920cd50d5 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -23,34 +23,6 @@ input_html: { rows: 8 }, wrapper: :with_block_label - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :favicon, - as: :file, - input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, - wrapper: :with_block_label - - .fields-row__column.fields-row__column-6.fields-group - - if @admin_settings.favicon.persisted? - = image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail' - = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do - = fa_icon 'trash fw' - = t('admin.site_uploads.delete') - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :app_icon, - as: :file, - input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, - wrapper: :with_block_label - - .fields-row__column.fields-row__column-6.fields-group - - if @admin_settings.app_icon.persisted? - = image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail' - = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do - = fa_icon 'trash fw' - = t('admin.site_uploads.delete') - .fields-row .fields-row__column.fields-row__column-6.fields-group = f.input :mascot, diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 769c0dafe81d5..71aac5ead1c1f 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -40,5 +40,33 @@ = fa_icon 'trash fw' = t('admin.site_uploads.delete') + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :favicon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.favicon.persisted? + = image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :app_icon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.app_icon.persisted? + = image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + .actions = f.button :button, t('generic.save_changes'), type: :submit