Skip to content

Commit 0761d85

Browse files
authored
[JENKINS-68934] Replace 'Loading' overlay with skeleton outline on Manage Jenkins pages (#10565)
2 parents abfe865 + ff0d920 commit 0761d85

File tree

9 files changed

+187
-15
lines changed

9 files changed

+187
-15
lines changed

core/src/main/resources/hudson/model/Job/configure.jelly

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ THE SOFTWARE.
3838

3939
<l:side-panel sticky="true">
4040
<l:app-bar title="${%Configure}" />
41-
<div id="tasks" />
41+
<div id="tasks">
42+
<l:skeleton type="side-panel" />
43+
</div>
4244
</l:side-panel>
4345

4446
<l:main-panel>
45-
<div class="behavior-loading"><l:spinner text="${%LOADING}"/></div>
46-
4747
<f:form method="post" class="jenkins-form" action="configSubmit" name="config" tableClass="config-table">
4848
<l:app-bar title="${%General}" headingLevel="h2">
4949
<p:config-disableBuild />
@@ -52,6 +52,8 @@ THE SOFTWARE.
5252
<j:set var="descriptor" value="${it.descriptor}" />
5353
<j:set var="instance" value="${it}" />
5454

55+
<l:skeleton />
56+
5557
<div class="jenkins-section jenkins-section--no-border jenkins-!-margin-top-3">
5658
<f:entry title="${%Description}" help="${app.markupFormatter.helpUrl}">
5759
<f:textarea name="description" value="${it.description}" codemirror-mode="${app.markupFormatter.codeMirrorMode}" codemirror-config="${app.markupFormatter.codeMirrorConfig}" previewEndpoint="/markupFormatter/previewDescription"/>

core/src/main/resources/hudson/model/Run/configure.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ THE SOFTWARE.
2929
<l:breadcrumb title="${%Edit Build Information}" />
3030
<l:main-panel>
3131
<l:app-bar title="${%Edit Build Information}" />
32-
<div class="behavior-loading"><l:spinner text="${%LOADING}"/></div>
32+
<l:skeleton />
3333
<f:form method="post" action="configSubmit" name="config">
3434
<j:set var="readOnlyMode" value="${!h.hasPermission(it,it.UPDATE)}"/>
3535
<f:entry title="${%DisplayName}" help="/help/run-config/displayName.html">

core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ l.layout(permission:app.SYSTEM_READ, title:my.displayName, cssclass:request2.get
1616

1717
set("readOnlyMode", !app.hasPermission(app.ADMINISTER))
1818

19-
p()
20-
div(class:"behavior-loading") {
21-
l.spinner(text: _("LOADING"))
22-
}
19+
l.skeleton()
2320
f.form(method:"post", name:"config", action:"configure", class: "jenkins-form") {
2421
set("instance", my)
2522
set("descriptor", my.descriptor)

core/src/main/resources/jenkins/appearance/AppearanceGlobalConfiguration/index.jelly

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ THE SOFTWARE.
4848
</l:notice>
4949
</j:when>
5050
<j:otherwise>
51-
<div class="behavior-loading">
52-
<l:spinner text="${%Loading}"/>
53-
</div>
51+
<l:skeleton />
5452

5553
<f:form method="post" name="config" action="configure">
5654
<j:set var="instance" value="${it}" />

core/src/main/resources/jenkins/model/Jenkins/configure.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ THE SOFTWARE.
3535
<j:set var="readOnlyMode" value="${!h.hasPermission(app.MANAGE)}"/>
3636
<l:app-bar title="${%System}" />
3737

38-
<div class="behavior-loading"><l:spinner text="${%LOADING}"/></div>
38+
<l:skeleton />
3939
<f:form method="post" name="config" action="configSubmit" class="jenkins-form">
4040
<j:set var="instance" value="${it}" />
4141
<j:set var="descriptor" value="${instance.descriptor}" />

core/src/main/resources/jenkins/tools/GlobalToolConfiguration/index.groovy

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ l.layout(permission:app.SYSTEM_READ, title:my.displayName, type:"one-column") {
1212
l.main_panel {
1313
l.app_bar(title: my.displayName)
1414

15-
div(class:"behavior-loading") {
16-
l.spinner(text: _("LOADING"))
17-
}
15+
l.skeleton()
1816

1917
f.form(method:"post",name:"config",action:"configure", class: "jenkins-form") {
2018
Functions.getSortedDescriptorsForGlobalConfigByDescriptor(my.FILTER).each { Descriptor descriptor ->
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!--
2+
The MIT License
3+
4+
Copyright (c) 2025, Jenkins contributors
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
THE SOFTWARE.
23+
-->
24+
25+
<?jelly escape-by-default='true'?>
26+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
27+
<st:documentation>
28+
<st:attribute name="type" use="optional" since="TODO">
29+
The type of skeleton, available options are 'form' and 'side-panel'.
30+
31+
Defaults to 'form'.
32+
</st:attribute>
33+
Placeholder skeleton element, hiding the rest of its parent container until the page has loaded
34+
</st:documentation>
35+
36+
<j:switch on="${attrs.type}">
37+
<j:case value="side-panel">
38+
<div class="jenkins-side-panel-skeleton">
39+
<div />
40+
<div />
41+
<div />
42+
<div />
43+
<div />
44+
</div>
45+
</j:case>
46+
<j:default>
47+
<div class="jenkins-form-skeleton">
48+
<span />
49+
<div />
50+
<span />
51+
<div />
52+
<span />
53+
<div />
54+
<span />
55+
<div />
56+
<span />
57+
<div />
58+
<span />
59+
<div />
60+
<span />
61+
<div />
62+
<span />
63+
<div />
64+
<span />
65+
<div />
66+
<span />
67+
<div />
68+
</div>
69+
</j:default>
70+
</j:switch>
71+
72+
<!-- :has isn't supported in HTMLUnit, so don't load the CSS in that case -->
73+
<j:if test="${!h.isUnitTest}">
74+
<st:adjunct includes="lib.layout.skeleton.skeleton" />
75+
</j:if>
76+
</j:jelly>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
.jenkins-side-panel-skeleton,
2+
.jenkins-form-skeleton {
3+
position: relative;
4+
display: flex;
5+
flex-direction: column;
6+
gap: 0.5rem;
7+
z-index: 1;
8+
9+
& > * {
10+
background: var(--button-background);
11+
border: var(--jenkins-border--subtle);
12+
border-radius: var(--form-input-border-radius);
13+
height: 2.375rem;
14+
animation: pulse-skeleton 2s both ease-in-out infinite;
15+
}
16+
17+
& > span {
18+
height: 1.5rem;
19+
opacity: 0.5;
20+
21+
&:nth-of-type(1) {
22+
width: 20%;
23+
}
24+
25+
&:nth-of-type(2) {
26+
width: 10%;
27+
}
28+
29+
&:nth-of-type(3) {
30+
width: 5%;
31+
}
32+
33+
&:nth-of-type(4) {
34+
width: 15%;
35+
}
36+
37+
&:nth-of-type(5) {
38+
width: 10%;
39+
}
40+
41+
&:nth-of-type(6) {
42+
width: 20%;
43+
}
44+
45+
&:nth-of-type(7) {
46+
width: 10%;
47+
}
48+
49+
&:nth-of-type(8) {
50+
width: 5%;
51+
}
52+
53+
&:nth-of-type(9) {
54+
width: 15%;
55+
}
56+
57+
&:nth-of-type(10) {
58+
width: 10%;
59+
}
60+
}
61+
62+
& > div {
63+
height: 2.375rem;
64+
margin-bottom: 1rem;
65+
}
66+
}
67+
68+
.jenkins-side-panel-skeleton {
69+
gap: 0.125rem;
70+
71+
& > div {
72+
margin: 0 -0.7rem;
73+
}
74+
}
75+
76+
@keyframes pulse-skeleton {
77+
50% {
78+
background: var(--button-background--active);
79+
}
80+
}
81+
82+
body:has(.jenkins-form-skeleton) {
83+
section,
84+
.jenkins-form-item,
85+
#bottom-sticker {
86+
display: none !important;
87+
}
88+
}

war/src/main/webapp/scripts/hudson-behavior.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,11 +1730,24 @@ function rowvgStartEachRow(recursive, f) {
17301730
e = null; // avoid memory leak
17311731
});
17321732

1733+
Behaviour.specify(
1734+
"DIV.jenkins-form-skeleton, DIV.jenkins-side-panel-skeleton",
1735+
"div-jenkins-form-skeleton",
1736+
++p,
1737+
function (e) {
1738+
e.remove();
1739+
},
1740+
);
1741+
17331742
Behaviour.specify(
17341743
"DIV.behavior-loading",
17351744
"div-behavior-loading",
17361745
++p,
17371746
function (e) {
1747+
console.warn(
1748+
".behavior-loading is deprecated, use <l:skeleton /> instead - since TODO",
1749+
e,
1750+
);
17381751
e.classList.add("behavior-loading--hidden");
17391752
},
17401753
);

0 commit comments

Comments
 (0)