Skip to content

Commit

Permalink
Modernise the table design (jenkinsci#5851)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Jacomb <[email protected]>
  • Loading branch information
janfaracik and timja authored Nov 10, 2021
1 parent 8522932 commit cee0d06
Show file tree
Hide file tree
Showing 43 changed files with 647 additions and 304 deletions.
48 changes: 47 additions & 1 deletion core/src/main/java/org/jenkins/ui/icon/IconSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@
*/
package org.jenkins.ui.icon;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.jelly.JellyContext;

/**
* An icon set.
Expand All @@ -37,13 +43,16 @@
*/
public class IconSet {


public static final IconSet icons = new IconSet();
private static final Map<String, String> IONICONS = new ConcurrentHashMap<>();

private Map<String, Icon> iconsByCSSSelector = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByUrl = new ConcurrentHashMap<>();
private Map<String, Icon> iconsByClassSpec = new ConcurrentHashMap<>();
private Map<String, Icon> coreIcons = new ConcurrentHashMap<>();

private static final String PLACEHOLDER_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"ionicon\" viewBox=\"0 0 512 512\"><title>Ellipse</title><circle cx=\"256\" cy=\"256\" r=\"192\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"32\"/></svg>";
private static final Icon NO_ICON = new Icon("_", "_", "_");

public IconSet() {
Expand All @@ -57,6 +66,43 @@ public static void initPageVariables(JellyContext context) {
context.setVariable("icons", icons);
}

private static String prependTitleIfRequired(String icon, String title) {
if (StringUtils.isNotBlank(title)) {
return "<span class=\"jenkins-visually-hidden\">" + title + "</span>" + icon;
}
return icon;
}

public static String getIonicon(String name, String title) {
if (IONICONS.containsKey(name)) {
String icon = IONICONS.get(name);
return prependTitleIfRequired(icon, title);
}

// Load icon if it exists
InputStream inputStream = IconSet.class.getResourceAsStream("/images/ionicons/" + name + ".svg");
String ionicon = null;

try {
if (inputStream != null) {
ionicon = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
} catch (IOException e) {
// ignored
}
if (ionicon == null) {
ionicon = PLACEHOLDER_SVG;
}

ionicon = ionicon.replaceAll("(<title>)[^&]*(</title>)", "$1$2");
ionicon = ionicon.replaceAll("<svg", "<svg aria-hidden=\"true\"");
ionicon = ionicon.replace("stroke:#000", "stroke:currentColor");

IONICONS.put(name, ionicon);

return prependTitleIfRequired(ionicon, title);
}

public IconSet addIcon(Icon icon) {
iconsByCSSSelector.put(icon.getNormalizedSelector(), icon);
if (icon.getUrl() != null) {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/resources/hudson/AboutJenkins/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ THE SOFTWARE.
<li><a href="https://fontawesome.com">Font Awesome</a>, created by <a href="https://twitter.com/davegandy">Dave Gandy</a>. <a href="https://scripts.sil.org/OFL">SIL OFL 1.1 License</a> and <a href="https://opensource.org/licenses/MIT">MIT License</a></li>
<li><a href="https://www.google.com/fonts/specimen/Roboto">Google Fonts: Roboto</a>, created by <a href="https://plus.google.com/110879635926653430880/about">Christian Robertson</a>. <a href="https://www.apache.org/licenses/LICENSE-2.0.html">Apache License, version 2.0</a></li>
<li><a href="https://github.com/jenkins-contrib-themes/jenkins-core-theme">Jenkins Contrib Themes</a>, created by <a href="https://github.com/afonsof">Afonso Franca</a>. <a href="https://opensource.org/licenses/MIT">MIT License</a></li>
<li><a href="https://ionic.io/ionicons">Ionicons</a>, created by <a href="https://github.com/ionic-team">Ionic</a>. <a href="https://github.com/ionic-team/ionicons/blob/master/LICENSE">MIT License</a></li>
</ul>
<h2>${%plugin.dependencies}</h2>
<ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ THE SOFTWARE.

<st:adjunct includes="org.kohsuke.stapler.simile.timeline" />

<div id="resizeContainer" style="height: 300px; border:1px solid black; padding-bottom: 5px;">
<div id="resizeContainer" style="height: 300px; border-radius: 10px; padding-bottom: 5px; margin-bottom: 2rem; overflow: hidden;">
<div id="tl" style="height:100%;"/>
</div>
<div id="status" />
Expand Down
1 change: 0 additions & 1 deletion core/src/main/resources/hudson/model/Computer/builds.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ THE SOFTWARE.
</p>

<st:include page="control.jelly" it="${it.timeline}" />
<div class="spacer" />

<t:buildListTable builds="${it.builds}"/>
</l:main-panel>
Expand Down
72 changes: 45 additions & 27 deletions core/src/main/resources/hudson/model/ComputerSet/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,62 @@ THE SOFTWARE.
<l:main-panel>
<j:set var="monitors" value="${it._monitors}"/>
<j:set var="tableWidth" value="${3}"/>
<table id="computers" class="sortable pane bigtable">
<tr>
<th width="32">S</th>
<th initialSortDir="down" align="left">${%Name}</th>
<j:forEach var="m" items="${monitors}">
<j:if test="${m.columnCaption!=null}">
<j:set var="tableWidth" value="${tableWidth+1}"/>
<th align="right" tooltip="${m.descriptor.timestampString}">${m.columnCaption}</th>
</j:if>
</j:forEach>
<th />
</tr>

<div class="jenkins-app-bar">
<div class="jenkins-app-bar__content">
<h1>${%Manage nodes and clouds}</h1>
</div>
<l:hasAdministerOrManage>
<div class="jenkins-app-bar__controls">
<form method="post" action="updateNow">
<s:submit value="${%Refresh status}"/>
</form>
</div>
</l:hasAdministerOrManage>
</div>

<table id="computers" class="jenkins-table sortable">
<thead>
<tr>
<th class="jenkins-table__cell--tight">S</th>
<th initialSortDir="down">${%Name}</th>
<j:forEach var="m" items="${monitors}">
<j:if test="${m.columnCaption!=null}">
<j:set var="tableWidth" value="${tableWidth+1}"/>
<th align="right" tooltip="${m.descriptor.timestampString}">${m.columnCaption}</th>
</j:if>
</j:forEach>
<th class="jenkins-table__cell--tight" data-sort-disable="true" />
</tr>
</thead>
<tbody>

<j:forEach var="c" items="${it._all}">
<tr id="node_${c.name}">
<td width="32" data="${c.icon}">
<l:icon class="${c.iconClassName} icon-lg" alt="${c.iconAltText}"/>
<td data="${c.icon}" class="jenkins-table__cell--tight">
<div class="jenkins-table__cell__button-wrapper">
<l:icon class="${c.iconClassName} icon-lg" alt="${c.iconAltText}"/>
</div>
</td>
<td><a href="${rootURL}/${c.url}" class="model-link inside">${c.displayName}</a></td>

<td>
<a href="${rootURL}/${c.url}" class="jenkins-table__link model-link inside">${c.displayName}</a>
</td>

<j:forEach var="m" items="${monitors}">
<j:if test="${m.columnCaption!=null}">
<j:set var="data" value="${m.data(c)}"/>
<st:include page="column.jelly" from="${m}" />
</j:if>
</j:forEach>

<td><!-- config link -->
<td class="jenkins-table__cell--tight">
<j:if test="${c.hasPermission(c.EXTENDED_READ)}">
<a href="${rootURL}/${c.url}configure">
<l:icon class="icon-gear icon-lg" tooltip="${c.hasPermission(c.CONFIGURE) ? '%Configure' : '%View Configuration'}"/>
</a>
<div class="jenkins-table__cell__button-wrapper">
<a href="${rootURL}/${c.url}configure" class="jenkins-table__button">
<l:ionicon name="settings-outline" />
</a>
</div>
</j:if>
</td>
</tr>
Expand All @@ -85,15 +110,8 @@ THE SOFTWARE.
</j:forEach>
<th />
</tr>

</tbody>
</table>
<l:hasAdministerOrManage>
<div align="right" style="margin-top:0.5em">
<form method="post" action="updateNow">
<s:submit value="${%Refresh status}"/>
</form>
</div>
</l:hasAdministerOrManage>
</l:main-panel>
</l:layout>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ THE SOFTWARE.
<l:main-panel>
<h1>${%Timeline}</h1>
<st:include page="control.jelly" it="${it.timeline}" />
<div class="spacer" />

<h1>${%Build Time Trend}</h1>
<div align="center">
Expand Down
44 changes: 28 additions & 16 deletions core/src/main/resources/hudson/model/View/AsynchPeople/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,36 @@ THE SOFTWARE.
<st:include page="sidepanel.jelly" it="${it.parent}" />
<t:setIconSize/>
<l:main-panel>
<h1>
<l:icon class="icon-user icon-xlg"/>
${%People}
<j:set var="viewType" value="${it.parent.class.simpleName}"/>
<j:set var="isAll" value="${viewType=='Hudson' or viewType=='AllView'}"/>
<j:if test="${!isAll}"> - ${it.parent.displayName}</j:if>
</h1>
<p id="asynch-people-description" data-iconsize="${iconSize}">${%blurb}</p>
<div class="jenkins-app-bar">
<div class="jenkins-app-bar__content">
<h1>
${%People}
<j:set var="viewType" value="${it.parent.class.simpleName}"/>
<j:set var="isAll" value="${viewType=='Hudson' or viewType=='AllView'}"/>
<j:if test="${!isAll}">- ${it.parent.displayName}</j:if>
</h1>
</div>
</div>
<p class="jenkins-description">${%blurb}</p>
<st:adjunct includes="hudson.model.View.AsynchPeople.people-resources" />
<l:progressiveRendering handler="${it}" callback="display"/>
<table class="sortable pane bigtable" id="people">
<tr>
<th class="minimum-width" data-sort-disable="true" />
<th>${%User ID}</th>
<th>${%Name}</th>
<th initialSortDir="up">${%Last Commit Activity}</th>
<th>${%On}</th>
</tr>

<!-- cloned from JS -->
<span id="person-circle-outline" class="default-hidden">
<l:ionicon name="person-circle-outline" />
</span>

<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''} sortable" id="people">
<thead>
<tr>
<th data-sort-disable="true" class="jenkins-table__cell--tight"></th>
<th>${%User ID}</th>
<th>${%Name}</th>
<th initialSortDir="up">${%Last Commit Activity}</th>
<th>${%On}</th>
</tr>
</thead>
<tbody></tbody>
</table>
<t:iconSize>
<j:if test="${!isAll}">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
function display(data) {
var p = document.getElementById('people');
var rootURL = document.head.getAttribute('data-rooturl');
var iconSize = document.getElementById("asynch-people-description").getAttribute("data-iconsize");
for (var x = 0; data.length > x; x++) {
var e = data[x];
var id = 'person-' + e.id;
Expand All @@ -17,14 +16,12 @@ function display(data) {
}

var d = document.createElement('td');
var a = document.createElement('a');
a.href = rootURL + "/" + e.url;
a.className = 'model-link inside';
var i = document.createElement('img');
i.src = e.avatar;
i.className = 'icon' + iconSize;
a.appendChild(i);
d.appendChild(a);
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')
wrapper.innerHTML = icon.children[0].outerHTML;
d.appendChild(wrapper);
r.appendChild(d);

d = document.createElement('td');
Expand All @@ -50,7 +47,7 @@ function display(data) {
if (e.projectUrl != null) {
a = document.createElement('a');
a.href = rootURL + "/" + e.projectUrl;
a.className = 'model-link inside';
a.className = 'jenkins-table__link model-link inside';
a.appendChild(document.createTextNode(e.projectFullDisplayName));
d.appendChild(a);
}
Expand Down
12 changes: 7 additions & 5 deletions core/src/main/resources/hudson/model/View/builds.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ THE SOFTWARE.
<l:layout title="${it.displayName}">
<st:include page="sidepanel.jelly" />
<l:main-panel>
<h1>
<l:icon class="icon-notepad icon-xlg"/>
${%buildHistory(it.class.name=='hudson.model.AllView' ? app.displayName : it.displayName)}
</h1>
<div class="jenkins-app-bar">
<div class="jenkins-app-bar__content">
<h1>
${%buildHistory(it.class.name=='hudson.model.AllView' ? app.displayName : it.displayName)}
</h1>
</div>
</div>

<j:if test="${!request.getParameter('suppressTimelineControl')}"> <!-- cf. BuildListTableTest; breaks HtmlUnit -->
<st:include page="control.jelly" it="${it.timeline}"/>
</j:if>
<div class="spacer" />

<t:buildListTable builds="${it.builds}"/>
</l:main-panel>
Expand Down
13 changes: 1 addition & 12 deletions core/src/main/resources/hudson/model/View/noJob.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,8 @@ THE SOFTWARE.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
<style type="text/css">
#noJobDiv {
margin: 20px 0px;
padding: 10px;
border-color: #BBBBBB;
}
</style>
<div class="pane-frame">
<div id="noJobDiv">
${%description_1}
<j:if test="${it.hasPermission(it.CONFIGURE)}">
${%description_2}
${%description_2}
</j:if>
</div>
</div>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ THE SOFTWARE.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<td>
<td class="jenkins-table__cell--tight">
<j:if test="${job.buildable and job.hasPermission(job.BUILD)}">
<j:set var="id" value="${h.generateId()}"/>
<j:set var="href" value="${jobBaseUrl}${job.shortUrl}build?delay=0sec"/>
<a href="${href}">
<j:set var="id" value="${h.generateId()}"/>
<j:set var="href" value="${jobBaseUrl}${job.shortUrl}build?delay=0sec"/>
<div class="jenkins-table__cell__button-wrapper">
<a id="${id}" class="jenkins-table__button jenkins-table__button--green" href="${href}">
<j:choose>
<j:when test="${job.parameterized}">
<j:set var="title" value="${%Schedule_a_task_with_parameters(h.getRelativeDisplayNameFrom(job, itemGroup),it.taskNoun(job))}"/>
Expand All @@ -38,11 +39,10 @@ THE SOFTWARE.
<j:set var="title" value="${%Schedule_a_task(h.getRelativeDisplayNameFrom(job, itemGroup),it.taskNoun(job))}"/>
</j:otherwise>
</j:choose>
<j:set var="icon" value="${app.queue.contains(job) ? 'icon-clock-anime' : 'icon-clock'}"/>
<l:icon class="${icon} ${subIconSizeClass}"
title="${title}" alt="${title}" id="${id}" />
<l:ionicon name="play-outline" title="${title}" />
<st:adjunct includes="hudson.views.BuildButtonColumn.icon"/>
</a>
</div>
</j:if>
</td>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<th width="1">
<st:nbsp/>
</th>
<j:jelly xmlns:j="jelly:core">
<th class="jenkins-table__cell--tight" />
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ THE SOFTWARE.
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<td style="${indenter.getCss(job)}">
<!-- TODO consider using ${rootURL}/${job.url} instead (also in other column.jelly) -->
<a href="${jobBaseUrl}${job.shortUrl}" class='model-link inside'> <l:breakable value="${h.getRelativeDisplayNameFrom(job, itemGroup)}"/></a>
<a href="${jobBaseUrl}${job.shortUrl}" class='jenkins-table__link model-link inside'> <l:breakable value="${h.getRelativeDisplayNameFrom(job, itemGroup)}"/></a>
</td>
</j:jelly>
Loading

0 comments on commit cee0d06

Please sign in to comment.