Add support for Ionicons (symbols) to <l:Icon> and <l:task> components (#6186)

This commit is contained in:
Jan Faracik authored and GitHub committed 2022-02-12 15:41:57 +00:00
1 parent 731a29786a
commit 81072df4f7
61 files changed
+314 -259

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
public class AboutJenkins extends ManagementLink {
@Override
public String getIconFileName() {
return "help.svg";
return "symbol-help-circle";
}
@Override
+63
View File
@@ -161,6 +161,7 @@ import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.apache.commons.lang.StringUtils;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.jvnet.tiger_types.Types;
import org.kohsuke.accmod.Restricted;
@@ -2290,6 +2291,68 @@ public class Functions {
}
}
@Restricted(NoExternalUse.class)
public static Icon tryGetIcon(String iconGuess) {
// Jenkins Symbols don't have metadata so return null
if (iconGuess == null || iconGuess.startsWith("symbol-")) {
return null;
}
StaplerRequest currentRequest = Stapler.getCurrentRequest();
currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse());
Icon iconMetadata = IconSet.icons.getIconByClassSpec(iconGuess);
if (iconMetadata == null) {
// Icon could be provided as a simple iconFileName e.g. "settings.png"
iconMetadata = IconSet.icons.getIconByClassSpec(IconSet.toNormalizedIconNameClass(iconGuess) + " icon-md");
}
if (iconMetadata == null) {
// Icon could be provided as an absolute iconFileName e.g. "/plugin/foo/abc.png"
iconMetadata = IconSet.icons.getIconByUrl(iconGuess);
}
return iconMetadata;
}
@Restricted(NoExternalUse.class)
public static String tryGetIconPath(String iconGuess, JellyContext context) {
if (iconGuess == null) {
return null;
}
if (iconGuess.startsWith("symbol-")) {
return iconGuess;
}
StaplerRequest currentRequest = Stapler.getCurrentRequest();
currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse());
String rootURL = currentRequest.getContextPath();
Icon iconMetadata = tryGetIcon(iconGuess);
String iconSource = null;
if (iconMetadata != null) {
iconSource = iconMetadata.getQualifiedUrl(context);
}
if (iconMetadata == null) {
if (!iconGuess.startsWith("/")) {
iconGuess = "/" + iconGuess;
}
iconSource = rootURL + (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : "") + iconGuess;
}
if (iconMetadata != null && iconMetadata.getClassSpec() != null) {
String translatedIcon = IconSet.tryTranslateTangoIconToSymbol(iconMetadata.getClassSpec());
if (translatedIcon != null) {
return translatedIcon;
}
}
return iconSource;
}
@SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "True randomness isn't necessary for form item IDs")
@Restricted(NoExternalUse.class)
public static String generateItemId() {
+6 -2
View File
@@ -78,15 +78,18 @@ import hudson.Functions;
*/
public interface Action extends ModelObject {
/**
* Gets the file name of the icon.
* Gets the name of the icon.
*
* @return
* If the icon name is prefixed with "symbol-", a Jenkins Symbol
* will be used.
* <p>
* If just a file name (like "abc.gif") is returned, it will be
* interpreted as a file name inside {@code /images/24x24}.
* This is useful for using one of the stock images.
* <p>
* If an absolute file name that starts from '/' is returned (like
* "/plugin/foo/abc.gif'), then it will be interpreted as a path
* "/plugin/foo/abc.gif"), then it will be interpreted as a path
* from the context root of Jenkins. This is useful to pick up
* image files from a plugin.
* <p>
@@ -94,6 +97,7 @@ public interface Action extends ModelObject {
* but this can be used for actions that only contribute {@code floatBox.jelly}
* and no task list item. The other case where this is useful is
* to avoid showing links that require a privilege when the user is anonymous.
* @see <a href="https://www.jenkins.io/doc/developer/views/symbols/">Jenkins Symbols</a>
* @see Functions#isAnonymous()
* @see Functions#getIconFilePath(Action)
*/
@@ -38,7 +38,7 @@ public class ManageJenkinsAction implements RootAction {
@Override
public String getIconFileName() {
if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ))
return "gear.png";
return "symbol-settings";
else
return null;
}
@@ -39,7 +39,7 @@ public class ConfigureLink extends ManagementLink {
@Override
public String getIconFileName() {
return "gear.svg";
return "symbol-settings";
}
@Override
@@ -133,6 +133,18 @@ public interface ModelObjectWithContextMenu extends ModelObject {
return this;
}
/** @since TODO */
public ContextMenu add(String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation) {
if (text != null && icon != null && url != null) {
MenuItem item = new MenuItem(url, icon, text);
item.iconXml = iconXml;
item.post = post;
item.requiresConfirmation = requiresConfirmation;
items.add(item);
}
return this;
}
/**
* Add a header row (no icon, no URL, rendered in header style).
*
@@ -268,6 +280,11 @@ public interface ModelObjectWithContextMenu extends ModelObject {
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "read by Stapler")
public String icon;
/**
* Optional icon XML, if set it's used instead of @icon for the menu item
*/
private String iconXml;
/**
* True to make a POST request rather than GET.
* @since 1.504
@@ -300,6 +317,11 @@ public interface ModelObjectWithContextMenu extends ModelObject {
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "read by Stapler")
public ContextMenu subMenu;
@Exported
public String getIconXml() {
return iconXml;
}
public MenuItem(String url, String icon, String displayName) {
withUrl(url).withIcon(icon).withDisplayName(displayName);
}
@@ -35,6 +35,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* An icon set.
@@ -45,7 +47,7 @@ public class IconSet {
public static final IconSet icons = new IconSet();
private static final Map<String, String> IONICONS = new ConcurrentHashMap<>();
private static final Map<String, String> SYMBOLS = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByCSSSelector = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByUrl = new ConcurrentHashMap<>();
@@ -73,34 +75,40 @@ public class IconSet {
return icon;
}
public static String getIonicon(String name, String title) {
if (IONICONS.containsKey(name)) {
String icon = IONICONS.get(name);
return prependTitleIfRequired(icon, title);
// for Jelly
@Restricted(NoExternalUse.class)
public static String getSymbol(String name, String title, String classes) {
if (SYMBOLS.containsKey(name)) {
String symbol = SYMBOLS.get(name);
symbol = symbol.replaceAll("(class=\")[^&]*?(\")", "$1$2");
symbol = symbol.replaceAll("<svg", "<svg class=\"" + classes + "\"");
return prependTitleIfRequired(symbol, title);
}
// Load icon if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/ionicons/" + name + ".svg");
String ionicon = null;
// Load symbol if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/symbols/" + name + ".svg");
String symbol = null;
try {
if (inputStream != null) {
ionicon = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
symbol = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
} catch (IOException e) {
// ignored
}
if (ionicon == null) {
ionicon = PLACEHOLDER_SVG;
if (symbol == null) {
symbol = PLACEHOLDER_SVG;
}
ionicon = ionicon.replaceAll("(<title>)[^&]*(</title>)", "$1$2");
ionicon = ionicon.replaceAll("<svg", "<svg aria-hidden=\"true\"");
ionicon = ionicon.replace("stroke:#000", "stroke:currentColor");
symbol = symbol.replaceAll("(<title>)[^&]*(</title>)", "$1$2");
symbol = symbol.replaceAll("(class=\")[^&]*?(\")", "$1$2");
symbol = symbol.replaceAll("<svg", "<svg aria-hidden=\"true\"");
symbol = symbol.replaceAll("<svg", "<svg class=\"" + classes + "\"");
symbol = symbol.replace("stroke:#000", "stroke:currentColor");
IONICONS.put(name, ionicon);
SYMBOLS.put(name, symbol);
return prependTitleIfRequired(ionicon, title);
return prependTitleIfRequired(symbol, title);
}
public IconSet addIcon(Icon icon) {
@@ -517,24 +525,34 @@ public class IconSet {
images.add("warning");
images.add("document-properties");
Map<String, String> materialIcons = new HashMap<>();
materialIcons.put("help", "svg-sprite-action-symbol.svg#ic_help_24px");
for (Map.Entry<String, String> size : sizes.entrySet()) {
for (String image : images) {
icons.addIcon(new Icon("icon-" + image + " " + size.getKey(),
"svgs/" + image + ".svg", size.getValue()));
}
for (Map.Entry<String, String> imageEntry : materialIcons.entrySet()) {
icons.addIcon(new Icon(
"icon-" + imageEntry.getKey() + " " + size.getKey(),
"material-icons/" + imageEntry.getValue(),
size.getValue(),
IconFormat.EXTERNAL_SVG_SPRITE
)
);
}
}
}
/**
* This is a temporary function to replace Tango icons across Jenkins and plugins with
* appropriate Jenkins Symbols
*
* @param tangoIcon A tango icon in the format 'icon-* size-*', e.g. 'icon-gear icon-lg'
* @return a Jenkins Symbol (if one exists) otherwise null
*/
@Restricted(NoExternalUse.class)
public static String tryTranslateTangoIconToSymbol(String tangoIcon) {
if (tangoIcon != null) {
tangoIcon = tangoIcon.split(" ")[0];
}
Map<String, String> translations = new HashMap<>();
translations.put("icon-clock", "symbol-play");
translations.put("icon-edit-delete", "symbol-trash");
translations.put("icon-gear", "symbol-settings");
translations.put("icon-gear2", "symbol-settings");
translations.put("icon-plugin", "symbol-plugins");
return translations.getOrDefault(tangoIcon, null);
}
}
@@ -39,7 +39,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${%Search}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
@@ -39,7 +39,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
@@ -180,7 +180,7 @@ THE SOFTWARE.
<form class="jenkins-buttons-row" method="post"
action="${rootURL}/updateCenter/plugin/${p.shortName}/downgrade">
<button class="jenkins-table__button jenkins-table__button--orange" tooltip="${%downgradeTo(p.backupVersion)}">
<l:ionicon name="downgrade-circle-outline"/>
<l:icon src="symbol-downgrade-circle"/>
<span class="jenkins-!-margin-left-1"
style="font-size: 0.75rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50px;">
${p.backupVersion}
@@ -226,7 +226,7 @@ THE SOFTWARE.
</j:if>
<form class="jenkins-buttons-row" method="post" action="plugin/${p.shortName}/uninstall">
<button class="jenkins-table__button jenkins-table__button--red uninstall" tooltip="${%Uninstall} ${p.updateInfo.displayName?:p.displayName}">
<l:ionicon name="close-circle-outline"/>
<l:icon src="symbol-close-circle"/>
</button>
</form>
</j:otherwise>
@@ -28,7 +28,7 @@ l.header()
l.side_panel {
l.tasks {
l.task(icon:"icon-up icon-md", href:rootURL+'/', title:_("Back to Dashboard"))
l.task(icon:"icon-gear icon-md", href:"${rootURL}/manage", title:_("Manage Jenkins"))
l.task(icon:"symbol-settings", href:"${rootURL}/manage", title:_("Manage Jenkins"))
if (!app.updateCenter.jobs.isEmpty()) {
l.task(icon:"icon-plugin icon-md", href:"${rootURL}/updateCenter/", title:_("Update Center"))
}
@@ -46,7 +46,7 @@ THE SOFTWARE.
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</div>
</div>
</l:app-bar>
@@ -33,9 +33,9 @@ THE SOFTWARE.
<l:task href=".." icon="icon-up icon-md" title="${%Back to Loggers}"/>
<l:task href="." icon="icon-notepad icon-md" title="${%Log records}"/>
<l:isAdmin>
<l:task href="configure" icon="icon-gear icon-md" title="${%Configure}"/>
<l:task href="configure" icon="symbol-settings" title="${%Configure}"/>
<l:task href="delete" icon="icon-edit-delete icon-md" title="${%Delete}"/>
</l:isAdmin>
</l:tasks>
</l:side-panel>
</j:jelly>
</j:jelly>
@@ -64,7 +64,7 @@ THE SOFTWARE.
<l:isAdmin>
<div class="jenkins-table__cell__button-wrapper">
<a href="${lr.searchUrl}/configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
<l:icon src="symbol-settings" />
</a>
</div>
</l:isAdmin>
@@ -31,14 +31,14 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" title="${%Manage Jenkins}"/>
<l:task href="." icon="icon-clipboard icon-md" title="${%Log Recorders}">
<l:isAdmin>
<l:task href="new" icon="icon-new-package icon-md" title="${%New Log Recorder}"/>
</l:isAdmin>
</l:task>
<l:task href="all" icon="icon-notepad icon-md" title="${%All Log Messages}"/>
<l:task href="levels" icon="icon-gear icon-md" title="${%Log Levels}"/>
<l:task href="levels" icon="symbol-settings" title="${%Log Levels}"/>
</l:tasks>
</l:side-panel>
</j:jelly>
@@ -31,5 +31,5 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${buildUrl.baseUrl}/" icon="icon-search icon-md" title="${%Status}"/>
<l:task href="${buildUrl.baseUrl}/changes" icon="icon-notepad icon-md" title="${%Changes}"/>
<p:console-link/>
<l:task href="${buildUrl.baseUrl}/configure" icon="icon-notepad icon-md" title="${h.hasPermission(it,it.UPDATE)?'%Edit Build Information':'%View Build Information'}"/>
</j:jelly>
<l:task href="${buildUrl.baseUrl}/configure" icon="symbol-settings" title="${h.hasPermission(it,it.UPDATE)?'%Edit Build Information':'%View Build Information'}"/>
</j:jelly>
@@ -85,7 +85,7 @@ THE SOFTWARE.
<j:if test="${c.hasPermission(c.EXTENDED_READ)}">
<div class="jenkins-table__cell__button-wrapper">
<a href="${rootURL}/${c.url}configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
<l:icon src="symbol-settings" />
</a>
</div>
</j:if>
@@ -32,11 +32,11 @@ THE SOFTWARE.
<j:getStatic var="createPermission" className="hudson.model.Computer" field="CREATE"/>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Manage Jenkins}"/>
<l:task href="new" icon="icon-new-computer icon-md" permission="${createPermission}" title="${%New Node}"/>
<l:task href="${rootURL}/configureClouds" icon="icon-health-40to59 icon-md" permission="${app.SYSTEM_READ}"
title="${app.hasPermission(app.ADMINISTER) ? '%Configure Clouds' : '%View Clouds'}"/>
<l:task href="configure" icon="icon-gear icon-md" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Node Monitoring}"/>
<l:task href="configure" icon="symbol-settings" permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%Node Monitoring}"/>
</l:tasks>
<t:queue items="${app.queue.items}" />
<t:executors />
@@ -34,10 +34,10 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task contextMenu="false" href="${url}" icon="icon-attribute icon-md" title="${%Overview}"/>
<j:if test="${it.atom}">
<l:task href="${url}/configure" icon="icon-gear icon-md" title="${%Configure}" permission="${app.ADMINISTER}" />
<l:task href="${url}/configure" icon="symbol-settings" title="${%Configure}" permission="${app.ADMINISTER}" />
</j:if>
<l:task href="${url}/load-statistics" icon="icon-monitor icon-md" title="${%Load Statistics}"/>
<st:include page="actions.jelly" />
</l:tasks>
</l:side-panel>
</j:jelly>
</j:jelly>
@@ -31,7 +31,7 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}"/>
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/manage" icon="symbol-settings" title="${%Manage Jenkins}"/>
<l:task href="${rootURL}/pluginManager/" icon="icon-plugin icon-lg" title="${%Manage Plugins}"/>
</l:tasks>
</l:side-panel>
@@ -34,7 +34,7 @@ THE SOFTWARE.
<l:task contextMenu="false" href="${rootURL}/asynchPeople/" icon="icon-up icon-md" title="${%People}"/>
<l:task contextMenu="false" href="${rootURL}/${it.url}/" icon="icon-search icon-md" title="${%Status}"/>
<l:task href="${rootURL}/${it.url}/builds" icon="icon-notepad icon-md" title="${%Builds}"/>
<l:task href="${rootURL}/${it.url}/configure" icon="icon-gear icon-md" permission="${app.ADMINISTER}" title="${%Configure}"/>
<l:task href="${rootURL}/${it.url}/configure" icon="symbol-settings" permission="${app.ADMINISTER}" title="${%Configure}"/>
<t:actions actions="${it.propertyActions}"/>
<t:actions actions="${it.transientActions}"/>
<j:if test="${it.canDelete()}">
@@ -43,11 +43,11 @@ THE SOFTWARE.
<l:progressiveRendering handler="${it}" callback="display"/>
<!-- cloned from JS -->
<span id="person-circle-outline" class="default-hidden">
<l:ionicon name="person-circle-outline" />
<span id="person-circle" class="default-hidden">
<l:icon src="symbol-person-circle" />
</span>
<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''} sortable" id="people">
<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''} sortable" id="people" style="display: none;">
<thead>
<tr>
<th data-sort-disable="true" class="jenkins-table__cell--tight"></th>
@@ -1,5 +1,6 @@
function display(data) {
var p = document.getElementById('people');
p.show();
var rootURL = document.head.getAttribute('data-rooturl');
for (var x = 0; data.length > x; x++) {
var e = data[x];
@@ -19,7 +20,7 @@ function display(data) {
var wrapper = document.createElement('div');
wrapper.className = 'jenkins-table__cell__button-wrapper';
d.className = 'jenkins-table__cell--tight jenkins-table__icon';
var icon = document.getElementById('person-circle-outline')
var icon = document.getElementById('person-circle')
wrapper.innerHTML = icon.children[0].outerHTML;
d.appendChild(wrapper);
r.appendChild(d);
@@ -54,7 +54,7 @@ THE SOFTWARE.
<l:task href="${rootURL}/${it.viewUrl}builds" icon="icon-notepad icon-md" title="${%Build History}"/>
<j:if test="${it.isEditable()}">
<!-- /configure URL on Jenkins object is overloaded with Jenkins's system config, so always use the explicit name. -->
<l:task href="${rootURL}/${it.viewUrl}configure" icon="icon-gear icon-md" permission="${it.CONFIGURE}" title="${%Edit View}"/>
<l:task href="${rootURL}/${it.viewUrl}configure" icon="symbol-settings" permission="${it.CONFIGURE}" title="${%Edit View}"/>
</j:if>
<j:if test="${it.owner.canDelete(it)}">
<l:task href="delete" icon="icon-edit-delete icon-md" permission="${it.DELETE}" title="${%Delete View}"/>
@@ -63,7 +63,7 @@ THE SOFTWARE.
<td>
<div class="jenkins-table__cell__button-wrapper">
<a href="${user.url}/configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
<l:icon src="symbol-settings" />
</a>
</div>
</td>
@@ -71,7 +71,7 @@ THE SOFTWARE.
<j:if test="${user.canDelete()}">
<div class="jenkins-table__cell__button-wrapper">
<a href="${user.url}/delete" class="jenkins-table__button jenkins-table__button--red">
<l:ionicon name="trash-outline" />
<l:icon src="symbol-trash" />
</a>
</div>
</j:if>
@@ -28,7 +28,7 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<l:task href="${rootURL}/" icon="icon-up icon-md" title="${%Back to Dashboard}" />
<l:task href="${rootURL}/manage" icon="icon-gear icon-md" permission="${app.MANAGE}" title="${%Manage Jenkins}" />
<l:task href="${rootURL}/manage" icon="symbol-settings" permission="${app.MANAGE}" title="${%Manage Jenkins}" />
<l:task href="." icon="icon-new-user icon-md" permission="${app.ADMINISTER}" title="${%Users}" />
<l:task href="addUser" icon="icon-new-user icon-md" permission="${app.ADMINISTER}" title="${%Create User}" />
</l:tasks>
@@ -39,7 +39,7 @@ THE SOFTWARE.
<j:set var="title" value="${%Schedule_a_task(h.getRelativeDisplayNameFrom(job, itemGroup),it.taskNoun(job))}"/>
</j:otherwise>
</j:choose>
<l:ionicon name="play-outline" title="${title}" />
<l:icon src="symbol-play" alt="${title}" />
<st:adjunct includes="hudson.views.BuildButtonColumn.icon"/>
</a>
</div>
@@ -78,7 +78,7 @@ THE SOFTWARE.
<div class="jenkins-search">
<input type="search" placeholder="${%find}" class="jenkins-search__input" />
<div class="jenkins-search__icon">
<l:ionicon name="search-outline" />
<l:icon src="symbol-search" />
</div>
</div>
<div id="no-builds" class="jenkins-pane__information" style="${page.runs.isEmpty() ? '' : 'display: none;'}">
@@ -10,7 +10,7 @@ l.layout(norefresh: true, permission: app.MANAGE, title: my.displayName) {
l.side_panel {
l.tasks {
l.task(icon: "icon-up icon-md", href: rootURL + '/', title: _("Back to Dashboard"))
l.task(icon: "icon-gear icon-md", href: "${rootURL}/manage", title: _("Manage Jenkins"))
l.task(icon: "symbol-settings", href: "${rootURL}/manage", title: _("Manage Jenkins"))
}
}
l.main_panel {
@@ -13,7 +13,7 @@ l.layout(norefresh:true, permission:app.SYSTEM_READ, title:my.displayName) {
l.side_panel {
l.tasks {
l.task(icon:"icon-up icon-md", href:rootURL+'/', title:_("Back to Dashboard"))
l.task(icon:"icon-gear icon-md", href:"${rootURL}/computer/", title:_("Manage Nodes"))
l.task(icon:"symbol-settings", href:"${rootURL}/computer/", title:_("Manage Nodes"))
}
}
l.app_bar(title: my.displayName)
@@ -54,34 +54,18 @@ THE SOFTWARE.
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.addHeader(category.key.label) : null}
<div class="jenkins-section__items">
<j:forEach var="m" items="${category.value}">
<j:set var="icon" value="${m.iconClassName != null ? m.iconClassName : m.iconFileName}" />
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(icon + ' icon-xlg')}"/>
<j:if test="${iconMetadata == null}">
<!-- Icon could be provided as a simple iconFileName e.g. "settings.png" -->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(icons.toNormalizedIconNameClass(icon) + ' icon-xlg')}"/>
</j:if>
<j:if test="${iconMetadata == null}">
<j:set var="iconMetadata" value="${icons.getIconByUrl(icon)}"/>
</j:if>
<j:if test="${icon!=null}">
<j:if test="${m.iconFileName != null}">
<div class="jenkins-section__item">
<j:set var="iconUrl" value="${icon.startsWith('/') ? resURL+icon : imagesURL + '/svgs/' + icon}" />
<j:set var="alt" value="${icon.replaceAll('\\d*\\.[^.]+$', '')}"/>
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(m.urlName, iconUrl, m.displayName, m.requiresPOST, m.requiresConfirmation) : null}
<j:set var="iconSrc" value="${h.tryGetIconPath(m.iconFileName, context)}"/>
<j:set var="iconXml">
<l:icon src="${m.iconFileName}" />
</j:set>
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(m.urlName, iconSrc, iconXml, m.displayName, m.requiresPOST, m.requiresConfirmation) : null}
<j:choose>
<j:when test="${m.requiresConfirmation}">
<l:confirmationLink href="${m.urlName}" post="${m.requiresPOST}" message="${%are.you.sure(m.displayName)}">
<j:choose>
<j:when test="${iconMetadata != null}">
<l:icon class="${iconMetadata.classSpec}"/>
</j:when>
<j:otherwise>
<l:icon src="${iconUrl}"
class="icon"
alt="[${%alt}]" />
</j:otherwise>
</j:choose>
<l:icon src="${m.iconFileName}" class="icon" />
<dl>
<dt>${m.displayName}</dt>
<dd><j:out value="${m.description}"/></dd>
@@ -91,17 +75,7 @@ THE SOFTWARE.
</j:when>
<j:when test="${m.requiresPOST}">
<f:link href="${m.urlName}" post="${m.requiresPOST}">
<j:choose>
<j:when test="${iconMetadata != null}">
<l:icon class="${iconMetadata.classSpec}"
alt="[${%alt}]"/>
</j:when>
<j:otherwise>
<l:icon src="${iconUrl}"
class="icon"
alt="[${%alt}]" />
</j:otherwise>
</j:choose>
<l:icon src="${m.iconFileName}" class="icon" />
<dl>
<dt>${m.displayName}</dt>
<dd><j:out value="${m.description}"/></dd>
@@ -111,17 +85,7 @@ THE SOFTWARE.
</j:when>
<j:otherwise>
<a href="${m.urlName}">
<j:choose>
<j:when test="${iconMetadata != null}">
<l:icon class="${iconMetadata.classSpec}"
alt="[${%alt}]"/>
</j:when>
<j:otherwise>
<l:icon src="${iconUrl}"
class="icon"
alt="[${%alt}]" />
</j:otherwise>
</j:choose>
<l:icon src="${m.iconFileName}" class="icon" />
<dl>
<dt>${m.displayName}</dt>
<dd><j:out value="${m.description}"/></dd>
@@ -11,7 +11,7 @@ l.layout(permission:app.SYSTEM_READ, title:my.displayName) {
l.side_panel {
l.tasks {
l.task(icon:"icon-up icon-md", href:rootURL+'/', title:_("Back to Dashboard"))
l.task(icon:"icon-gear icon-md", href:"${rootURL}/manage", title:_("Manage Jenkins"))
l.task(icon:"symbol-settings", href:"${rootURL}/manage", title:_("Manage Jenkins"))
}
}
l.app_bar(title: my.displayName)
@@ -27,10 +27,10 @@
<input name="q" placeholder="${searchPlaceholder}" id="search-box" class="main-search__input" value="${request.getParameter('q')}" role="searchbox" />
<span class="main-search__icon-leading">
<l:ionicon name="search-outline"/>
<l:icon src="symbol-search"/>
</span>
<a href="${searchHelpUrl}" class="main-search__icon-trailing">
<l:ionicon name="help-circle-outline"/>
<l:icon src="symbol-help-circle"/>
</a>
<div id="search-box-completion" />
@@ -33,6 +33,6 @@ THE SOFTWARE.
</st:documentation>
<st:adjunct includes="lib.form.repeatable.repeatable"/>
<button tooltip="${h.xmlEscape(attrs.value ?: '%Delete')}" class="repeatable-delete danger" type="button">
<l:ionicon name="close-outline" />
<l:icon src="symbol-close" />
</button>
</j:jelly>
@@ -44,4 +44,4 @@ THE SOFTWARE.
</j:if>
</st:include>
</j:forEach>
</j:jelly>
</j:jelly>
@@ -56,7 +56,7 @@ THE SOFTWARE.
tr.insert(new Element('td', {class: 'jenkins-table__cell--tight'}).
insert(new Element('div', {class: 'jenkins-table__cell__button-wrapper'}).
insert(new Element('a', {class: 'jenkins-table__button', href: '${rootURL}/' + e.url + 'console'}).
insert(`<l:ionicon name="terminal-outline" title="${%Console output}" />`))));
insert(`<l:icon src="symbol-terminal" alt="${%Console output}" />`))));
p.insert(tr);
Behaviour.applySubtree(tr);
}
@@ -94,7 +94,7 @@ THE SOFTWARE.
<div class="jenkins-search">
<s:textbox placeholder="${%Type for suggestions}" clazz="jenkins-search__input" name="from" field="copyNewItemFrom" onfocus="$('copy').click()" />
<div class="jenkins-search__icon">
<l:ionicon name="search-outline" />
<l:icon src="symbol-search" />
</div>
</div>
</div>
@@ -38,10 +38,10 @@ THE SOFTWARE.
</j:if>
<j:choose>
<j:when test="${h.hasPermission(it,it.CONFIGURE)}">
<l:task href="${url}/configure" icon="icon-gear icon-md" title="${%Configure}"/>
<l:task href="${url}/configure" icon="symbol-settings" title="${%Configure}"/>
</j:when>
<j:when test="${h.hasPermission(it,it.EXTENDED_READ)}">
<l:task href="${url}/configure" icon="icon-gear icon-md" title="${%View Configuration}"/>
<l:task href="${url}/configure" icon="symbol-settings" title="${%View Configuration}"/>
</j:when>
</j:choose>
<l:task confirmationMessage="${%delete.confirm(it.pronoun, it.displayName)}" href="${url}/doDelete" icon="icon-edit-delete icon-md" permission="${it.DELETE}" post="true" requiresConfirmation="true" title="${%delete(it.pronoun)}"/>
@@ -28,11 +28,9 @@ THE SOFTWARE.
Displays a link with a large icon. Used in the project top page.
<st:attribute name="icon" use="required">
The icon class name e.g. 'icon-folder'.
On older versions of Jenkins, this attribute value used to be a link to the raw icon image, where relative
paths were resolved against images/48x48 and absolute paths (that start with '/') were resolved against
the context root of Hudson.
Supports Jenkins Symbols (e.g. 'symbol-search')
Supports path to bitmap image (e.g. '/plugin/foo/abc.gif')
Supports icon class specification (e.g. 'icon-help icon-sm', 'icon-blue icon-md', 'icon-blue-anime icon-xlg')
</st:attribute>
<st:attribute name="href">
where the summary icon links to.
@@ -48,49 +46,16 @@ THE SOFTWARE.
<j:if test="${attrs.icon!=null}">
<tr>
<td>
<!--
Backward compatibility. Ugly but needed for now.
'icon' can be an icon class name (e.g. 'icon-folder'), but can also be an icon file name
(without path e.g. 'folder.png'), or can be a full path and name (e.g. to an icon in a
plugin - '/plugin/images/48x48/xxxx.png'). So, first we see can we derive an icon
from the 'icon' value, which will work if it's just something like 'folder.png'. Failing
that, we try deriving it by URL (assuming it's a full icon path). If all that fails,
we just fall back to the old method by passing a 'src' attribute to the <icon> tag.
-->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(icon + ' icon-xlg')}"/>
<j:if test="${iconMetadata == null}">
<!-- Icon could be provided as a simple iconFileName e.g. "settings.png" -->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(icons.toNormalizedIconNameClass(icon) + ' icon-xlg')}"/>
</j:if>
<j:if test="${iconMetadata == null}">
<j:set var="iconMetadata" value="${icons.getIconByUrl(icon)}"/>
</j:if>
<j:choose>
<j:when test="${attrs.href!=null}">
<a href="${attrs.href}">
<j:choose>
<j:when test="${iconMetadata != null}">
<l:icon class="${iconMetadata.classSpec}"/>
</j:when>
<j:otherwise>
<l:icon src="${icon.startsWith('/') ? resURL+icon : imagesURL+'/48x48/'+icon}"
alt="" style="width: 48px; height: 48px; margin-right:1em;" />
</j:otherwise>
</j:choose>
<l:icon src="${icon}"
alt="" style="width: 48px; height: 48px; margin-right:1em;" />
</a>
</j:when>
<j:otherwise>
<j:choose>
<j:when test="${iconMetadata != null}">
<l:icon class="${iconMetadata.classSpec}"/>
</j:when>
<j:otherwise>
<l:icon src="${icon.startsWith('/') ? resURL+icon : imagesURL+'/48x48/'+icon}"
alt="" style="width: 48px; height: 48px; margin-right:1em;" />
</j:otherwise>
</j:choose>
<l:icon src="${icon}"
alt="" style="width: 48px; height: 48px; margin-right:1em;" />
</j:otherwise>
</j:choose>
</td>
@@ -109,4 +74,4 @@ THE SOFTWARE.
</tr>
</j:if>
</j:if>
</j:jelly>
</j:jelly>
@@ -23,8 +23,13 @@ var breadcrumbs = (function() {
var logger = function() {};
// logger = function() { console.log.apply(console,arguments) }; // uncomment this line to enable logging
function makeMenuHtml(icon,displayName) {
function makeMenuHtml(icon, iconXml, displayName) {
var displaynameSpan = '<span>' + displayName + '</span>';
if (iconXml != null) {
return iconXml + displaynameSpan;
}
if (icon === null) return "<span style='margin: 2px 4px 2px 2px;' />" + displaynameSpan;
// TODO: move this to the API response in a clean way
@@ -213,9 +218,9 @@ var breadcrumbs = (function() {
var a = x.responseText.evalJSON().items;
function fillMenuItem(e) {
if (e.header) {
e.text = makeMenuHtml(e.icon, "<span class='header'>" + e.displayName + "</span>");
e.text = makeMenuHtml(e.icon, e.iconXml, "<span class='header'>" + e.displayName + "</span>");
} else {
e.text = makeMenuHtml(e.icon, e.displayName);
e.text = makeMenuHtml(e.icon, e.iconXml, e.displayName);
}
if (e.subMenu!=null)
e.subMenu = {id:"submenu"+(iota++), itemdata:e.subMenu.items.each(fillMenuItem)};
@@ -287,7 +292,7 @@ var breadcrumbs = (function() {
* @return {breadcrumbs.MenuItem}
*/
"add" : function (url,icon,displayName) {
this.items.push({ url:url, text:makeMenuHtml(icon,displayName) });
this.items.push({ url:url, text:makeMenuHtml(icon, null, displayName) });
return this;
}
};
+44 -35
View File
@@ -26,7 +26,15 @@ THE SOFTWARE.
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout">
<st:documentation>
@since 1.576
<st:attribute name="class">
<st:attribute name="src" use="optional">
Icon source
Supports Jenkins Symbols (e.g. 'symbol-search')
Supports path to bitmap image
Only relevant if the 'class' attribute is NOT specified.
</st:attribute>
<st:attribute name="class" use="optional">
The icon class specification e.g. 'icon-help icon-sm', 'icon-blue icon-md', 'icon-blue-anime icon-xlg'.
</st:attribute>
@@ -34,56 +42,57 @@ THE SOFTWARE.
ID of the icon element. Since TODO.
</st:attribute>
<st:attribute name="src">
Icon source raw URL. Only relevant if the 'class' attribute is NOT specified.
</st:attribute>
<st:attribute name="onclick" deprecated="true">onclick handler. Deprecated; assign an ID and look up the element that way to attach event handlers.</st:attribute>
<st:attribute name="title">title</st:attribute>
<st:attribute name="title" deprecated="true">title, deprecated use tooltip instead</st:attribute>
<st:attribute name="style">style</st:attribute>
<st:attribute name="tooltip">tooltip</st:attribute>
<st:attribute name="alt">alt</st:attribute>
</st:documentation>
<j:set var="iconSrc" value="${attrs.src}"/>
<j:set var="iconSrc" value="${h.tryGetIconPath(src, context)}"/>
<j:set var="iconMetadata" value="${h.tryGetIcon(src)}"/>
<j:set var="imgStyle" value="${attrs.style}"/>
<j:choose>
<j:when test="${attrs.class != null}">
<!-- Derive the icon url and style from the class spec -->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(attrs.class)}"/>
</j:when>
<j:otherwise>
<!-- Attempt to work back from the image url (backward compatibility)... -->
<j:set var="iconMetadata" value="${icons.getIconByUrl(iconSrc)}"/>
</j:otherwise>
</j:choose>
<j:if test="${iconSrc == null}">
<j:set var="iconSrc" value="${h.tryGetIconPath(class, context)}"/>
<j:set var="iconMetadata" value="${h.tryGetIcon(class)}"/>
</j:if>
<j:if test="${iconMetadata != null}">
<j:set var="iconSrc" value="${iconMetadata.getQualifiedUrl(context)}"/>
<j:set var="imgStyle" value="${iconMetadata.style} ${attrs.style}"/>
</j:if>
<j:choose>
<j:when test="${iconMetadata.buildStatus}">
<j:set var="outerLayer" value="${iconMetadata.inProgress ? 'build-status-in-progress' : 'build-status-static'}" />
<span class="build-status-icon__wrapper ${attrs.class}" style="${imgStyle}">
<span class="build-status-icon__outer">
<l:svgIcon href="${rootURL}/images/build-status/build-status-sprite.svg#${outerLayer}" id="${attrs.id}" tooltip="${attrs.tooltip}"/>
</span>
<l:svgIcon class="${attrs.class}" href="${iconSrc}" id="${attrs.id}" tooltip="${attrs.tooltip}"/>
</span>
<j:when test="${iconSrc.startsWith('symbol-')}">
<j:invokeStatic var="icon" className="org.jenkins.ui.icon.IconSet" method="getSymbol">
<j:arg value="${iconSrc.substring(7)}" />
<j:arg value="${alt ?: ''}" />
<j:arg value="${iconMetadata.classSpec ?: ''}" />
</j:invokeStatic>
<j:out value="${icon}" />
</j:when>
<j:when test="${iconMetadata.svgSprite and !iconMetadata.buildStatus}">
<l:svgIcon class="${attrs.class}" href="${iconSrc}" id="${attrs.id}"/>
</j:when>
<j:otherwise>
<img class="${attrs.class}" src="${iconSrc}" style="${imgStyle}" title="${attrs.title}" height="${attrs.height}"
alt="${attrs.alt}" width="${attrs.width}" onclick="${attrs.onclick}" tooltip="${attrs.tooltip}" id="${attrs.id}" />
<j:choose>
<j:when test="${iconMetadata.buildStatus}">
<j:set var="outerLayer" value="${iconMetadata.inProgress ? 'build-status-in-progress' : 'build-status-static'}" />
<span class="build-status-icon__wrapper ${attrs.class}" style="${imgStyle}">
<span class="build-status-icon__outer">
<l:svgIcon href="${rootURL}/images/build-status/build-status-sprite.svg#${outerLayer}" id="${attrs.id}" tooltip="${attrs.tooltip}"/>
</span>
<l:svgIcon class="${attrs.class}" href="${iconSrc}" id="${attrs.id}" tooltip="${attrs.tooltip}"/>
</span>
</j:when>
<j:when test="${iconMetadata.svgSprite and !iconMetadata.buildStatus}">
<l:svgIcon class="${attrs.class}" href="${iconSrc}" id="${attrs.id}"/>
</j:when>
<j:otherwise>
<img class="${attrs.class}" src="${iconSrc}" style="${imgStyle}" title="${attrs.title}" height="${attrs.height}"
alt="${attrs.alt}" width="${attrs.width}" onclick="${attrs.onclick}" tooltip="${attrs.tooltip}" id="${attrs.id}" />
</j:otherwise>
</j:choose>
</j:otherwise>
</j:choose>
</j:jelly>
@@ -1,18 +0,0 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:d="jelly:define">
<st:documentation>
Provides ionicon icons
<st:attribute name="name" use="required">
The name of the icon
</st:attribute>
<st:attribute name="title">
Title to use for the icon
</st:attribute>
</st:documentation>
<j:invokeStatic var="icon" className="org.jenkins.ui.icon.IconSet" method="getIonicon">
<j:arg value="${attrs.name}"/>
<j:arg value="${attrs.title ?: ''}"/>
</j:invokeStatic>
<j:out value="${icon}" />
</j:jelly>
+14 -27
View File
@@ -37,6 +37,7 @@ THE SOFTWARE.
Common values include:
# "symbol-search" / "symbol-terminal" / "symbol-settings" - use a Jenkins Symbol as the icon for your task
# "icon-folder icon-md" is an example of using a class spec for a medium folder icon
# "images/24x24/..." then points to the stock icon resources
# "plugin/foobar/abc/def.png" that points to "src/main/webapp/abc/def.png" in your plugin resources
@@ -56,7 +57,7 @@ THE SOFTWARE.
This is useful for showing links to restricted pages, as showing
them to unprivileged users don't make sense.
If both permission and permissions is set, then permissions will be used
</st:attribute>
<st:attribute name="permissions">
@@ -113,6 +114,10 @@ THE SOFTWARE.
</j:when>
<j:set var="match" value="${isCurrent or hasMatchingChild}" />
<j:if test="${attrs.permission != null}">
<j:set var="hasPermission" value="${h.hasPermission(it, attrs.permission)}" />
</j:if>
<!--
The icon attribute can be an icon class spec, or can be the url of an icon image.
Within core Jenkins, we've updated all usages of the task element to use icon class specs,
@@ -123,26 +128,12 @@ THE SOFTWARE.
icon class spec from the icon metadata, otherwise we create an icon element instance using
icon attribute value as an icon "src" (Vs class spec).
-->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(attrs.icon)}"/>
<j:if test="${iconMetadata == null}">
<!-- Icon could be provided as a simple iconFileName e.g. "settings.png" -->
<j:set var="iconMetadata" value="${icons.getIconByClassSpec(icons.toNormalizedIconNameClass(attrs.icon) + ' icon-md')}"/>
</j:if>
<j:if test="${iconMetadata == null}">
<!-- Icon could be provided as an absolute iconFileName e.g. "/plugin/foo/abc.png" -->
<j:set var="iconMetadata" value="${icons.getIconByUrl(attrs.icon)}"/>
</j:if>
<j:if test="${!icon.startsWith('/')}">
<j:set var="icon" value="/${icon}"/>
</j:if>
<j:set var="icon" value="${rootURL}${icon.startsWith('/images/') || icon.startsWith('/plugin/') ? h.resourcePath : ''}${icon}"/>
<j:when test="${enabled==false}">
<div class="task disabled">
<span class="task-link-wrapper">
<span class="task-disabled-link">
<span class="task-icon-link">
<l:icon alt="" class="${iconMetadata != null ? iconMetadata.classSpec : ''}" src="${iconMetadata ?: icon}" />
<l:icon src="${icon}" />
</span>
<span class="task-link-text">${title}</span>
</span>
@@ -153,15 +144,11 @@ THE SOFTWARE.
<j:otherwise>
<div class="task ">
<span class="task-link-wrapper ${watch ? 'task--active' : ''}">
<j:choose>
<j:when test="${iconMetadata != null}">
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(href, iconMetadata.getQualifiedUrl(context), title, post == 'true', requiresConfirmation == 'true') : null}
</j:when>
<j:otherwise>
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(href, icon, title, post == 'true', requiresConfirmation == 'true') : null}
</j:otherwise>
</j:choose>
<j:set var="iconSrc" value="${h.tryGetIconPath(icon, context)}"/>
<j:set var="iconXml">
<l:icon src="${icon}" />
</j:set>
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(href, iconSrc, iconXml, title, post == 'true', requiresConfirmation == 'true') : null}
<j:set var="id" value="${h.generateId()}"/>
<j:if test="${attrs.onclick == null and post and not requiresConfirmation}">
<script>
@@ -177,7 +164,7 @@ THE SOFTWARE.
<j:when test="${requiresConfirmation and not attrs.onClick}">
<l:confirmationLink class="task-link ${isCurrent ? 'task-link--active' : ''}" href="${href}" post="${post}" message="${confirmationMessage ?: title}">
<span class="task-icon-link">
<l:icon alt="" class="${iconMetadata != null ? iconMetadata.classSpec : ''}" src="${iconMetadata ?: icon}" />
<l:icon src="${icon}" />
</span>
<span>${title}</span>
</l:confirmationLink>
@@ -186,7 +173,7 @@ THE SOFTWARE.
<j:otherwise>
<a href="${href}" class="task-link ${isCurrent ? 'task-link--active' : ''}" onclick="${attrs.onclick ?: (post ? 'return postRequest_' + id + '(this)' : null)}" title="${title}">
<span class="task-icon-link">
<l:icon alt="" class="${iconMetadata != null ? iconMetadata.classSpec : ''}" src="${iconMetadata ?: icon}" />
<l:icon src="${icon}" />
</span>
<span class="task-link-text">${title}</span>
</a>
+11 -6
View File
@@ -141,16 +141,17 @@ public class IconTest {
DomElement tasksDiv = p.getElementById("tasks");
List<DomElement> taskDivs = StreamSupport.stream(tasksDiv.getChildElements().spliterator(), false).collect(Collectors.toList());
assertIconToImageOkay(taskDivs.get(0).getElementsByTagName("img").get(0), "/images/svgs/up.svg", "icon-up icon-md");
assertIconToImageOkay(taskDivs.get(0).getElementsByTagName("img").get(0), "/images/svgs/up.svg");
// this is loading the png from cloudbees-folder plugin
// when this is swapped to an SVG and the dep updated this test will need to change
assertIconToImageOkay(taskDivs.get(1).getElementsByTagName("img").get(0), "/images/svgs/folder.svg", "icon-folder icon-md");
assertIconToImageOkay(taskDivs.get(2).getElementsByTagName("img").get(0), "/images/svgs/package.svg", "icon-package icon-xlg");
assertIconToImageOkay(taskDivs.get(3).getElementsByTagName("img").get(0), "/images/svgs/package.svg", "icon-package icon-xlg");
assertIconToImageOkay(taskDivs.get(4).getElementsByTagName("img").get(0), "/images/svgs/package.svg", "icon-package icon-xlg");
assertIconToImageOkay(taskDivs.get(1).getElementsByTagName("img").get(0), "/images/svgs/folder.svg");
assertIconToImageOkay(taskDivs.get(2).getElementsByTagName("img").get(0), "/images/svgs/package.svg");
assertIconToImageOkay(taskDivs.get(3).getElementsByTagName("img").get(0), "/images/svgs/package.svg");
assertIconToImageOkay(taskDivs.get(4).getElementsByTagName("img").get(0), "/images/svgs/package.svg");
assertIconToSymbolOkay(taskDivs.get(5).getElementsByTagName("svg").get(0));
assertIconToImageOkay(taskDivs.get(5).getElementsByTagName("img").get(0), "/plugin/xxx/icon.png");
assertIconToImageOkay(taskDivs.get(6).getElementsByTagName("img").get(0), "/plugin/xxx/icon.png");
assertIconToImageOkay(taskDivs.get(7).getElementsByTagName("img").get(0), "/plugin/xxx/icon.png");
}
@TestExtension("testTasks")
@@ -180,6 +181,10 @@ public class IconTest {
}
}
private void assertIconToSymbolOkay(DomElement icon) {
assertThat("svg", is(icon.getTagName()));
}
private void dump(HtmlElement element) throws TransformerException {
System.out.println("****");
System.out.println(toString(element));
@@ -35,6 +35,7 @@ THE SOFTWARE.
<l:task href=".." icon="svgs/package.svg" title="Title"/>
<l:task href=".." icon="images/svgs/package.svg" title="Title"/>
<l:task href=".." icon="/images/svgs/package.svg" title="Title"/>
<l:task href=".." icon="symbol-settings" title="Title"/>
<!-- unknown icon urls... -->
<l:task href=".." icon="/plugin/xxx/icon.png" title="Title"/>
@@ -43,4 +44,4 @@ THE SOFTWARE.
</l:main-panel>
</l:layout>
</j:jelly>
</j:jelly>
@@ -163,6 +163,14 @@ div.yahooTree td {
#jenkins .yuimenuitem {
font-size: var(--font-size-xs);
padding: 3px;
svg,
img {
width: 1.2rem !important;
height: 1.2rem !important;
margin: 0.25rem 0.5rem 0.25rem 0;
color: var(--primary) !important;
}
}
#jenkins .yuimenuitem,
+6 -3
View File
@@ -92,12 +92,15 @@
min-height: 48px;
}
.icon, .icon-xlg {
.icon, .icon-xlg, svg {
position: relative;
width: 48px;
height: 48px;
min-width: 48px !important;
min-height: 48px !important;
width: 48px !important;
height: 48px !important;
pointer-events: none;
margin: 0 1rem 0 0;
color: var(--primary) !important;
}
dt {
@@ -87,9 +87,19 @@
// Target only icons withink task link in order to keep backwards compatibility
.task .task-icon-link {
display: inline-block;
display: inline-flex;
align-items: center;
justify-content: center;
margin: 0;
margin-right: 0.5rem;
width: 1.5rem;
height: 1.5rem;
svg {
width: 1.5rem;
height: 1.5rem;
color: var(--primary);
}
}
// Reset the margin for the icon links
.task .task-icon-link img,
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-q</title><path d="M262.29,192.31a64,64,0,1,0,57.4,57.4A64.13,64.13,0,0,0,262.29,192.31ZM416.39,256a154.34,154.34,0,0,1-1.53,20.79l45.21,35.46A10.81,10.81,0,0,1,462.52,326l-42.77,74a10.81,10.81,0,0,1-13.14,4.59l-44.9-18.08a16.11,16.11,0,0,0-15.17,1.75A164.48,164.48,0,0,1,325,400.8a15.94,15.94,0,0,0-8.82,12.14l-6.73,47.89A11.08,11.08,0,0,1,298.77,470H213.23a11.11,11.11,0,0,1-10.69-8.87l-6.72-47.82a16.07,16.07,0,0,0-9-12.22,155.3,155.3,0,0,1-21.46-12.57,16,16,0,0,0-15.11-1.71l-44.89,18.07a10.81,10.81,0,0,1-13.14-4.58l-42.77-74a10.8,10.8,0,0,1,2.45-13.75l38.21-30a16.05,16.05,0,0,0,6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16,16,0,0,0-6.07-13.94l-38.19-30A10.81,10.81,0,0,1,49.48,186l42.77-74a10.81,10.81,0,0,1,13.14-4.59l44.9,18.08a16.11,16.11,0,0,0,15.17-1.75A164.48,164.48,0,0,1,187,111.2a15.94,15.94,0,0,0,8.82-12.14l6.73-47.89A11.08,11.08,0,0,1,213.23,42h85.54a11.11,11.11,0,0,1,10.69,8.87l6.72,47.82a16.07,16.07,0,0,0,9,12.22,155.3,155.3,0,0,1,21.46,12.57,16,16,0,0,0,15.11,1.71l44.89-18.07a10.81,10.81,0,0,1,13.14,4.58l42.77,74a10.8,10.8,0,0,1-2.45,13.75l-38.21,30a16.05,16.05,0,0,0-6.05,14.08C416.17,247.67,416.39,251.83,416.39,256Z" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Extension Puzzle</title><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M413.66 246.1H386a2 2 0 01-2-2v-77.24A38.86 38.86 0 00345.14 128H267.9a2 2 0 01-2-2V98.34c0-27.14-21.5-49.86-48.64-50.33a49.53 49.53 0 00-50.4 49.51V126a2 2 0 01-2 2H87.62A39.74 39.74 0 0048 167.62V238a2 2 0 002 2h26.91c29.37 0 53.68 25.48 54.09 54.85.42 29.87-23.51 57.15-53.29 57.15H50a2 2 0 00-2 2v70.38A39.74 39.74 0 0087.62 464H158a2 2 0 002-2v-20.93c0-30.28 24.75-56.35 55-57.06 30.1-.7 57 20.31 57 50.28V462a2 2 0 002 2h71.14A38.86 38.86 0 00384 425.14v-78a2 2 0 012-2h28.48c27.63 0 49.52-22.67 49.52-50.4s-23.2-48.64-50.34-48.64z"/></svg>

After

Width:  |  Height:  |  Size: 763 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(50.378500, 42.000000)" stroke="currentColor" stroke-width="32">
<path d="M211.9115,150.31 C185.005783,147.652748 159.332619,162.189611 147.769706,186.628874 C136.206793,211.068138 141.24875,240.137201 160.366525,259.254975 C179.484299,278.37275 208.553362,283.414707 232.992626,271.851794 C257.431889,260.288881 271.968752,234.615717 269.3115,207.71 C266.263249,177.360931 242.260569,153.358251 211.9115,150.31 L211.9115,150.31 Z M410.9165,277.125 C411.707607,281.564887 410.903766,286.141508 408.647035,290.04607 L372.853966,351.97469 C370.607176,355.862053 367.040281,358.81433 362.8015,360.295 C358.482479,361.803699 353.868429,362.272089 349.334332,361.662103 L320.522752,357.785994 C299.879684,355.00882 279.988607,366.61094 272.243485,385.946453 L261.688643,412.296365 C259.969284,416.588704 257.239668,420.402837 253.7315,423.415 C250.285888,426.373452 245.894629,428 241.353189,428 L169.796876,428 C165.308275,428 160.960823,426.431216 157.5065,423.565 C153.996701,420.652754 151.25415,416.924675 149.518855,412.70702 L138.358864,385.582532 C130.453683,366.368896 110.519057,354.968685 89.9503733,357.89869 L61.8696222,361.898788 C57.3434346,362.543543 52.7286498,362.09476 48.4115003,360.59 C44.1729946,359.112652 40.6059709,356.162306 38.3598585,352.276117 L2.56596537,290.34607 C0.309233973,286.441508 -0.494606827,281.864887 0.296500273,277.425 C1.10103427,272.909758 2.99390287,268.65863 5.81124857,265.039618 L23.4896432,242.330895 C36.4067894,225.738218 36.3997393,202.490855 23.4725314,185.906016 L5.73914207,163.155131 C2.93726987,159.560495 1.06449867,155.330725 0.286500273,150.84 C-0.479118527,146.420732 0.330889273,141.87293 2.57525137,137.989769 L38.3890342,76.0253095 C40.6358245,72.1379474 44.2027191,69.1856702 48.4415003,67.705 C52.7614677,66.1959703 57.3747149,65.7192136 61.9119868,66.3128907 L91.2373143,70.1499486 C111.792442,72.839474 131.546967,61.2195013 139.18365,41.9470371 L149.578712,15.7133384 C151.281675,11.4156234 154.004147,7.5964633 157.5115,4.585 C160.957112,1.6265481 165.348371,0 169.889811,0 L241.446124,0 C245.934725,0 250.282177,1.5687841 253.7365,4.435 C257.245602,7.3466683 259.982278,11.0789954 261.704008,15.3012287 L272.612929,42.053395 C280.490247,61.3711009 300.470109,72.8820729 321.133157,70.007291 L349.370217,66.0787615 C353.899388,65.4486329 358.513486,65.904939 362.8315,67.41 C367.070006,68.8873483 370.637029,71.837694 372.883142,75.7238833 L408.677035,137.65393 C410.933766,141.558492 411.737607,146.135113 410.9465,150.575 C410.140427,155.098876 408.314604,159.379748 405.607548,163.092841 L385.561411,190.588825 C373.755974,206.781577 374.583692,228.954539 387.563545,244.222054 L405.168289,264.929585 C408.130553,268.41394 410.114244,272.622543 410.9165,277.125 Z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB