commit c4787d0e540ac3ff1bd1a3971b41f95519558ada Author: Marius BÄ‚LTEANU Date: Tue Jun 25 23:43:55 2024 +0200 Replace images with SVG icons. diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg new file mode 100644 index 000000000..202099574 --- /dev/null +++ b/app/assets/images/icons.svg @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 8ab85f269..6166c6250 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -238,8 +238,8 @@ div + .drdn-items {border-top:1px solid #ccc;} .contextual .drdn-content {top:18px;} .contextual .drdn-items {padding:2px; min-width: 160px;} -.contextual .drdn-items>a {padding: 5px 8px;} -.contextual .drdn-items>a.icon {padding-left: 24px; background-position-x: 4px;} +.contextual .drdn-items>a {display: flex; padding: 5px 8px;} +.contextual .drdn-items>a.icon:not(.icon-svg) {padding-left: 24px; background-position-x: 4px;} .contextual .drdn-items>a:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;} #project-jump.drdn {width:200px;display:inline-block;} @@ -499,7 +499,7 @@ div.square { width: .6em; height: .6em; } .contextual {float:right; white-space: nowrap; line-height:1.4em;margin:5px 0px; padding-left: 10px; font-size:0.9em;} -.contextual .icon {padding-top: 2px; padding-bottom: 3px;} +.contextual .icon:not(.icon-svg) {padding-top: 2px; padding-bottom: 3px;} .contextual input, .contextual select {font-size:0.9em;} .message .contextual { margin-top: 0; } @@ -1622,12 +1622,13 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { } /***** Icons *****/ -.icon { +.icon:not(.icon-svg) { background-position: 0% 50%; background-repeat: no-repeat; padding-left: 20px; } -.icon-only { + +.icon-only:not(.icon-svg) { background-position: 0% 50%; background-repeat: no-repeat; padding-left: 16px; @@ -1640,17 +1641,46 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { font-size: 8px; vertical-align: middle; } -.icon-only::after { +.icon-only:not(.icon-svg)::after { content: "\a0"; } -.icon-add { background-image: url(/add.png); } -.icon-edit { background-image: url(/edit.png); } -.icon-copy { background-image: url(/copy.png); } +.icon.icon-svg { + display: inline-flex; +} + +.icon-only .icon-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + white-space: nowrap; + border-width: 0; +} + +svg.s14 { + width: 14px; + height: 14px; +} + +a svg, .icon svg { + fill: currentColor; + margin-right: 4px; +} + +a.icon:hover svg { + fill: #c61a1a; +} + +.icon-add:not(.icon-svg) { background-image: url(/add.png); } +.icon-edit:not(.icon-svg) { background-image: url(/edit.png); } +.icon-copy:not(.icon-svg) { background-image: url(/copy.png); } .icon-duplicate { background-image: url(/duplicate.png); } -.icon-del { background-image: url(/delete.png); } +.icon-del:not(.icon-svg) { background-image: url(/delete.png); } .icon-move { background-image: url(/move.png); } -.icon-save { background-image: url(/save.png); } +.icon-:not(.icon-svg) { background-image: url(/save.png); } .icon-download { background-image: url(/download.png); } .icon-cancel { background-image: url(/cancel.png); } .icon-multiple { background-image: url(/table_multiple.png); } @@ -1663,18 +1693,18 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-attachment { background-image: url(/attachment.png); } .icon-history { background-image: url(/history.png); } .icon-time-entry, .icon-time { background-image: url(/time.png); } -.icon-time-add { background-image: url(/time_add.png); } -.icon-stats { background-image: url(/stats.png); } +.icon-time-add:not(.icon-svg) { background-image: url(/time_add.png); } +.icon-stats:not(.icon-svg) { background-image: url(/stats.png); } .icon-warning { background-image: url(/warning.png); } .icon-error { background-image: url(/exclamation.png); } -.icon-fav { background-image: url(/fav.png); } -.icon-fav-off { background-image: url(/fav_off.png); } -.icon-reload { background-image: url(/reload.png); } +.icon-fav:not(.icon-svg) { background-image: url(/fav.png); } +.icon-fav-off:not(.icon-svg) { background-image: url(/fav_off.png); } +.icon-reload:not(.icon-svg) { background-image: url(/reload.png); } .icon-lock, .icon-locked { background-image: url(/locked.png); } .icon-unlock { background-image: url(/unlock.png); } -.icon-checked { background-image: url(/toggle_check.png); } +.icon-checked:not(.icon-svg) { background-image: url(/toggle_check.png); } .icon-report { background-image: url(/report.png); } -.icon-comment, .icon-comments { background-image: url(/comment.png); } +.icon-comment:not(.icon-svg), .icon-comments.not(.icon-svg) { background-image: url(/comment.png); } .icon-summary { background-image: url(/lightning.png); } .icon-server-authentication { background-image: url(/server_key.png); } .icon-issue { background-image: url(/ticket.png); } @@ -1688,11 +1718,11 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-email-add { background-image: url(/email_add.png); } .icon-ok { background-image: url(/true.png); } .icon-not-ok { background-image: url(/false.png); } -.icon-link-break { background-image: url(/link_break.png); } +.icon-link-break:not(.icon-svg) { background-image: url(/link_break.png); } .icon-list { background-image: url(/text_list_bullets.png); } .icon-close { background-image: url(/close.png); } .icon-close:hover { background-image: url(/close_hl.png); } -.icon-settings { background-image: url(/changeset.png); } +.icon-settings:not(.icon-svg) { background-image: url(/changeset.png); } .icon-group, .icon-groupnonmember, .icon-groupanonymous { background-image: url(/group.png); } .icon-roles { background-image: url(/database_key.png); } .icon-issue-edit { background-image: url(/ticket_edit.png); } @@ -1710,7 +1740,7 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-project { background-image: url(/projects.png); } .icon-add-bullet { background-image: url(/bullet_add.png); } .icon-shared { background-image: url(/link.png); } -.icon-actions { background-image: url(/3_bullets.png); } +.icon-actions:not(.icon-svg) { background-image: url(/3_bullets.png); } .icon-sort-handle { background-image: url(/reorder.png); } .icon-expanded { background-image: url(/arrow_down.png); } .icon-collapsed { background-image: url(/arrow_right.png); } @@ -1722,7 +1752,7 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-toggle-plus { background-image: url(/bullet_toggle_plus.png) } .icon-toggle-minus { background-image: url(/bullet_toggle_minus.png) } .icon-clear-query { background-image: url(/close_hl.png); } -.icon-import { background-image: url(/database_go.png); } +.icon-import:not(.icon-svg) { background-image: url(/database_go.png); } .icon-file { background-image: url(/files/default.png); } .icon-file.text-plain { background-image: url(/files/text.png); } @@ -1742,7 +1772,7 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container { .icon-file.application-pdf { background-image: url(/files/pdf.png); } .icon-file.application-zip { background-image: url(/files/zip.png); } .icon-file.application-gzip { background-image: url(/files/zip.png); } -.icon-copy-link { background-image: url(/copy_link.png); } +.icon-copy-link:not(.icon-svg) { background-image: url(/copy_link.png); } .sort-handle.ajax-loading { background-image: url(/loading.gif); } tr.ui-sortable-helper { border:1px solid #e4e4e4; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d7d588eb3..ca3cc449d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,9 +28,11 @@ class ApplicationController < ActionController::Base include Redmine::Hook::Helper include RoutesHelper include AvatarsHelper + include IconsHelper helper :routes helper :avatars + helper :icons class_attribute :accept_api_auth_actions class_attribute :accept_atom_auth_actions diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5a0e9b0b4..93ef4d996 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -824,7 +824,7 @@ module ApplicationHelper content = capture(&block) if content.present? trigger = - content_tag('span', l(:button_actions), :class => 'icon-only icon-actions', + content_tag('span', icon('actions', :label => l(:button_actions), :icon_only => true), :class => 'icon-only icon-actions icon-svg', :title => l(:button_actions)) trigger = content_tag('span', trigger, :class => 'drdn-trigger') content = content_tag('div', content, :class => 'drdn-items') @@ -1567,7 +1567,7 @@ module ApplicationHelper end def link_to_context_menu - link_to l(:button_actions), '#', title: l(:button_actions), class: 'icon-only icon-actions js-contextmenu' + link_to icon('actions', :label => l(:button_actions), :icon_only => true), '#', title: l(:button_actions), class: 'icon-only icon-actions js-contextmenu icon-svg' end # Helper to render JSON in views @@ -1897,8 +1897,8 @@ module ApplicationHelper def copy_object_url_link(url) link_to_function( - l(:button_copy_link), 'copyTextToClipboard(this);', - class: 'icon icon-copy-link', + icon('copy-link', :label => l(:button_copy_link)), 'copyTextToClipboard(this);', + class: 'icon icon-copy-link icon-svg', data: {'clipboard-text' => url} ) end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb new file mode 100644 index 000000000..044f2eb69 --- /dev/null +++ b/app/helpers/icons_helper.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module IconsHelper + DEFAULT_ICON_SIZE = "14" + + # Generates an SVG icon from a sprite + def icon(icon_name, label: nil, icon_only: false, size: DEFAULT_ICON_SIZE) + sprite_path = "icons.svg" + + options = {} + options[:class] = "s#{size}" + options[:aria] = {hidden: true} + + sprite_icon(sprite_path, icon_name, options) + (icon_label(label) if label) + end + + private + + # Generates the SVG tag for the icon + # + # @param icon_name [String] the name of the icon in the sprite + # @return [String] HTML safe string containing the SVG + def sprite_icon(sprite_path, icon_name, options) + content_tag( + :svg, + content_tag(:use, '', { 'href' => "#{asset_path(sprite_path)}#icon--#{icon_name}" }), + options + ) + end + + # Generates the label for the icon + # + # @param label [String] the text of the label + # @return [String] HTML safe string containing the label + def icon_label(label) + content_tag(:span, label, class: "icon-label") + end +end \ No newline at end of file diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 4784096ac..64e98aaf5 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -207,13 +207,13 @@ module IssuesHelper buttons = if manage_relations link_to( - l(:label_relation_delete), + icon('link-break', :label => l(:label_relation_delete), :icon_only => true), relation_path(relation, issue_id: issue.id), :remote => true, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :title => l(:label_relation_delete), - :class => 'icon-only icon-link-break' + :class => 'icon-only icon-link-break icon-svg' ) else "".html_safe diff --git a/app/helpers/watchers_helper.rb b/app/helpers/watchers_helper.rb index c9d14d95f..c95fa5fb4 100644 --- a/app/helpers/watchers_helper.rb +++ b/app/helpers/watchers_helper.rb @@ -26,7 +26,8 @@ module WatchersHelper return '' unless objects.any? watched = Watcher.any_watched?(objects, user) - css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') + icon = watched ? 'fav' : 'fav-off' + css = [watcher_css(objects), 'icon-svg', 'icon', icon].join(' ') text = watched ? l(:button_unwatch) : l(:button_watch) url = watch_path( :object_type => objects.first.class.to_s.underscore, @@ -34,7 +35,7 @@ module WatchersHelper ) method = watched ? 'delete' : 'post' - link_to text, url, :remote => true, :method => method, :class => css + link_to icon(icon, :label => text), url, :remote => true, :method => method, :class => css end # Returns the css class used to identify watch links for a given +object+ diff --git a/app/views/issues/_action_menu.html.erb b/app/views/issues/_action_menu.html.erb index 1904f2171..cf4a5a906 100644 --- a/app/views/issues/_action_menu.html.erb +++ b/app/views/issues/_action_menu.html.erb @@ -1,16 +1,16 @@
-<%= link_to l(:button_edit), edit_issue_path(@issue), +<%= link_to icon('edit', :label => l(:button_edit)), edit_issue_path(@issue), :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', - :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> -<%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), - :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %> + :class => 'icon icon-edit icon-svg', :accesskey => accesskey(:edit) if @issue.editable? %> +<%= link_to icon('time-add', :label => l(:button_log_time)), new_issue_time_entry_path(@issue), + :class => 'icon icon-time-add icon-svg' if User.current.allowed_to?(:log_time, @project) %> <%= watcher_link(@issue, User.current) %> -<%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), - :class => 'icon icon-copy' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %> +<%= link_to icon('copy', :label => l(:button_copy)), project_copy_issue_path(@project, @issue), + :class => 'icon icon-copy icon-svg' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %> <%= actions_dropdown do %> <%= copy_object_url_link(issue_url(@issue, only_path: false)) %> - <%= link_to l(:button_delete_object, object_name: l(:label_issue).downcase), issue_path(@issue), + <%= link_to icon('del', :label => l(:button_delete_object, object_name: l(:label_issue).downcase)), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, - :method => :delete, :class => 'icon icon-del' if @issue.deletable? %> + :method => :delete, :class => 'icon icon-del icon-svg' if @issue.deletable? %> <% end %>
diff --git a/app/views/issues/index.html.erb b/app/views/issues/index.html.erb index 1ab5198a1..95c19b3cb 100644 --- a/app/views/issues/index.html.erb +++ b/app/views/issues/index.html.erb @@ -1,19 +1,19 @@
<% if User.current.allowed_to?(:add_issues, @project, :global => true) && (@project.nil? || Issue.allowed_target_trackers(@project).any?) %> - <%= link_to l(:label_issue_new), _new_project_issue_path(@project), :class => 'icon icon-add new-issue' %> + <%= link_to icon('add', :label => l(:label_issue_new)), _new_project_issue_path(@project), :class => 'icon icon-add icon-svg new-issue' %> <% end %> <%= actions_dropdown do %> <% if @project %> - <%= link_to l(:field_summary), project_issues_report_path(@project), :class => 'icon icon-stats' %> + <%= link_to icon('stats', :label => l(:field_summary)), project_issues_report_path(@project), :class => 'icon icon-svg icon-stats' %> <% end %> <% if User.current.allowed_to?(:import_issues, @project, :global => true) %> - <%= link_to l(:button_import), new_issues_import_path(:project_id => @project), :class => 'icon icon-import' %> + <%= link_to icon('import', :label => l(:button_import)), new_issues_import_path(:project_id => @project), :class => 'icon icon-import icon-svg' %> <% end %> - <%= link_to_if_authorized l(:label_settings), + <%= link_to_if_authorized icon('settings', :label => l(:label_settings)), {:controller => 'projects', :action => 'settings', :id => @project, :tab => 'issues'}, - :class => 'icon icon-settings' if User.current.allowed_to?(:edit_project, @project) %> + :class => 'icon icon-settings icon-svg' if User.current.allowed_to?(:edit_project, @project) %> <% end %>
diff --git a/app/views/issues/show.html.erb b/app/views/issues/show.html.erb index 3b713b4e7..bcbe701b8 100644 --- a/app/views/issues/show.html.erb +++ b/app/views/issues/show.html.erb @@ -84,7 +84,7 @@ end %>
- <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if @issue.notes_addable? %> + <%= link_to icon('comment', :label => l(:button_quote)), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment icon-svg' if @issue.notes_addable? %>

<%=l(:field_description)%>

diff --git a/app/views/queries/_query_form.html.erb b/app/views/queries/_query_form.html.erb index f60fa7911..b9893df82 100644 --- a/app/views/queries/_query_form.html.erb +++ b/app/views/queries/_query_form.html.erb @@ -53,13 +53,13 @@

- <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %> - <%= link_to l(:button_clear), { :set_filter => 1, :sort => '', :project_id => @project }, :class => 'icon icon-reload' %> + <%= link_to_function icon('checked', :label => l(:button_apply)), '$("#query_form").submit()', :class => 'icon icon-checked icon-svg' %> + <%= link_to icon('reload', :label => l(:button_clear)), { :set_filter => 1, :sort => '', :project_id => @project }, :class => 'icon icon-reload icon-svg' %> <% if @query.new_record? %> <% if User.current.allowed_to?(:save_queries, @project, :global => true) %> - <%= link_to_function l(:button_save_object, object_name: l(:label_query).downcase), + <%= link_to_function icon('save', :label => l(:button_save_object, object_name: l(:label_query).downcase)), "$('#query_type').prop('disabled',false);$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit()", - :class => 'icon icon-save' %> + :class => 'icon icon-save icon-svg' %> <% end %> <% else %> <% if @query.editable_by?(User.current) %>