From cc63c9c8e50a27b78874ac67f48584e232c4e1b5 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue, 10 Dec 2024 21:37:09 +0000 Subject: [PATCH 01/12] Refine command palette --- .../js/components/command-palette/index.js | 18 ++++- .../scss/components/_command-palette.scss | 73 +++++++++++++------ src/main/scss/components/_dialogs.scss | 6 ++ src/main/scss/form/_search-bar.scss | 2 +- 4 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/main/js/components/command-palette/index.js b/src/main/js/components/command-palette/index.js index dfb3c6eb7efe..5ece1106aed0 100644 --- a/src/main/js/components/command-palette/index.js +++ b/src/main/js/components/command-palette/index.js @@ -94,18 +94,23 @@ function init() { } searchResultsContainer.style.height = searchResults.offsetHeight + "px"; + debouncedSpinner.cancel(); commandPaletteSearchBarContainer.classList.remove( "jenkins-search--loading", ); }); } + const debouncedSpinner = debounce(() => { + commandPaletteSearchBarContainer.classList.add("jenkins-search--loading"); + }, 150); + const debouncedLoad = debounce(() => { renderResults(); }, 150); commandPaletteInput.addEventListener("input", () => { - commandPaletteSearchBarContainer.classList.add("jenkins-search--loading"); + debouncedSpinner(); debouncedLoad(); }); @@ -119,7 +124,16 @@ function init() { } function hideCommandPalette() { - commandPalette.close(); + commandPalette.setAttribute("closing", ""); + + commandPalette.addEventListener( + "animationend", + () => { + commandPalette.removeAttribute("closing"); + commandPalette.close(); + }, + { once: true }, + ); } function itemMouseEnter(item) { diff --git a/src/main/scss/components/_command-palette.scss b/src/main/scss/components/_command-palette.scss index 6c3b16517e3a..8aadde54608d 100644 --- a/src/main/scss/components/_command-palette.scss +++ b/src/main/scss/components/_command-palette.scss @@ -1,13 +1,5 @@ @use "../abstracts/mixins"; -.jenkins-command-palette__dialog { - &::backdrop { - background: var(--command-palette-backdrop-background); - backdrop-filter: contrast(0.7) brightness(0.9) saturate(1.25) blur(3px); - animation: jenkins-modal-backdrop-animate-in 0.3s; - } -} - .jenkins-command-palette__dialog { background: none; border: none; @@ -17,6 +9,41 @@ max-width: 100vw !important; margin: 0 !important; padding: 0 !important; + user-select: none; + + &::backdrop { + background: var(--command-palette-backdrop-background); + backdrop-filter: contrast(0.7) brightness(0.9) saturate(1.25) blur(3px); + animation: jenkins-dialog-backdrop-animate-in 0.1s linear; + } + + &[open] { + animation: command-palette-animate-in 0.1s cubic-bezier(0, 0.68, 0.5, 1.5); + } + + &[closing] { + animation: command-palette-animate-out 0.1s linear; + + &::backdrop { + animation: jenkins-dialog-backdrop-animate-out 0.1s linear; + } + } +} + +@keyframes command-palette-animate-in { + from { + translate: 0 4px; + scale: 98.5%; + opacity: 0; + transform: rotateX(30deg); + } +} + +@keyframes command-palette-animate-out { + to { + scale: 98.5%; + opacity: 0; + } } .jenkins-command-palette__wrapper { @@ -40,9 +67,7 @@ --search-bar-height: 3rem !important; background: transparent; - box-shadow: - 0 0 0 20px transparent, - var(--command-palette-inset-shadow); + box-shadow: var(--command-palette-inset-shadow); margin-bottom: 1.5rem; border-radius: 1rem; transition: var(--standard-transition); @@ -71,7 +96,8 @@ border-radius: 1rem; backdrop-filter: var(--command-palette-results-backdrop-filter); box-shadow: var(--command-palette-inset-shadow); - height: 0; + // If set to 0, Safari won't always show the backdrop-filter + height: 1px; transition: height var(--standard-transition); overflow: hidden; will-change: height; @@ -83,8 +109,8 @@ padding: 0.5rem; &__heading { - font-weight: 600; - font-size: 0.85rem; + font-weight: 500; + font-size: 0.875rem; margin: 0; padding: 0.75rem 0.75rem 0.625rem; color: var(--text-color-secondary); @@ -113,8 +139,7 @@ justify-content: flex-start; background: transparent; padding: 0.75rem; - border-radius: 0.66rem; - font-weight: 600; + border-radius: 0.5rem; cursor: pointer; color: var(--text-color) !important; transition: var(--standard-transition); @@ -132,17 +157,17 @@ display: flex; align-items: center; justify-content: center; - width: 1.4rem; - height: 1.4rem; - margin-right: 12.5px; + width: 1.375rem; + height: 1.375rem; + margin-right: 0.75rem; overflow: hidden; pointer-events: none; color: var(--text-color); svg, img { - width: 1.2rem; - height: 1.2rem; + width: 1.125rem; + height: 1.125rem; } } @@ -163,10 +188,10 @@ } &__info { - font-weight: 600; - font-size: 0.85rem; + font-size: 0.875rem; margin: 0; - padding: 12.5px 12.5px 10px; + padding: 0 14px; + line-height: 46px; color: var(--text-color); span { diff --git a/src/main/scss/components/_dialogs.scss b/src/main/scss/components/_dialogs.scss index 7040c70af0e2..fbefb7bbe275 100644 --- a/src/main/scss/components/_dialogs.scss +++ b/src/main/scss/components/_dialogs.scss @@ -87,6 +87,12 @@ $jenkins-dialog-padding: 1.3rem; } } +@keyframes jenkins-dialog-backdrop-animate-out { + to { + opacity: 0; + } +} + @keyframes jenkins-dialog-animate-in { from { scale: 85%; diff --git a/src/main/scss/form/_search-bar.scss b/src/main/scss/form/_search-bar.scss index ab4f6e9718cf..e520f9c7e69b 100644 --- a/src/main/scss/form/_search-bar.scss +++ b/src/main/scss/form/_search-bar.scss @@ -133,7 +133,7 @@ place-self: center center; opacity: 0; scale: 0; - filter: blur(5px); + filter: blur(2.5px); } &::after { From d43a8d3b2f4121e234ef16a7b4723e271cd9c43e Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue, 10 Dec 2024 21:54:23 +0000 Subject: [PATCH 02/12] Init --- core/src/main/java/hudson/model/Computer.java | 5 ++ core/src/main/java/hudson/model/Job.java | 5 ++ core/src/main/java/hudson/model/User.java | 6 ++ core/src/main/java/hudson/model/View.java | 5 ++ core/src/main/java/hudson/search/Search.java | 33 ++++++++- .../main/java/hudson/search/SearchItem.java | 9 +++ core/src/main/java/jenkins/model/Jenkins.java | 27 ++++++- .../components/command-palette/datasources.js | 3 +- .../js/components/command-palette/index.js | 18 ++++- .../js/components/command-palette/symbols.js | 1 - .../scss/components/_command-palette.scss | 73 +++++++++++++------ .../main/resources/images/symbols/jobs.svg | 5 ++ 12 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 war/src/main/resources/images/symbols/jobs.svg diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 0c525dfabe84..383361ee9a85 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1109,6 +1109,11 @@ public String getSearchUrl() { return getUrl(); } + @Override + public String getSearchIcon() { + return this.getIconClassName(); + } + /** * {@link RetentionStrategy} associated with this computer. * diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 7d140656e363..643e3d186e85 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -519,6 +519,11 @@ public boolean supportsLogRotator() { return true; } + @Override + public String getSearchIcon() { + return "symbol-status-" + this.getIconColor().getIconName(); + } + @Override protected SearchIndexBuilder makeSearchIndex() { return super.makeSearchIndex().add(new SearchIndex() { diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 685a80e540a9..85495f93ec0b 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -44,6 +44,7 @@ import hudson.security.AccessControlled; import hudson.security.SecurityRealm; import hudson.security.UserMayOrMayNotExistException2; +import hudson.tasks.UserAvatarResolver; import hudson.util.FormValidation; import hudson.util.RunList; import hudson.util.XStream2; @@ -277,6 +278,11 @@ public String getId() { return "/user/" + Util.rawEncode(idStrategy().keyFor(id)); } + @Override + public String getSearchIcon() { + return UserAvatarResolver.resolve(this, "48x48"); + } + /** * The URL of the user page. */ diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index b17077cde44d..722254572c0c 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -560,6 +560,11 @@ public String getSearchUrl() { return getUrl(); } + @Override + public String getSearchIcon() { + return "symbol-jobs"; + } + /** * Returns the transient {@link Action}s associated with the top page. * diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index 9bd624e34742..a3c76a7fc9d5 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -46,6 +46,8 @@ import jenkins.security.stapler.StaplerNotDispatchable; import jenkins.util.MemoryReductionUtil; import jenkins.util.SystemProperties; +import org.jenkins.ui.symbol.Symbol; +import org.jenkins.ui.symbol.SymbolRequest; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Ancestor; @@ -157,9 +159,16 @@ public void doSuggestOpenSearch(StaplerRequest2 req, StaplerResponse2 rsp, @Quer */ public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter String query) throws IOException, ServletException { Result r = new Result(); - for (SuggestedItem item : getSuggestions(req, query)) - r.suggestions.add(new Item(item.getPath(), item.getUrl())); + for (SuggestedItem item : getSuggestions(req, query)) { + String symbolName = item.item.getSearchIcon(); + if (symbolName == null || !symbolName.startsWith("symbol-")) { + symbolName = "symbol-search"; + } + + r.suggestions.add(new Item(item.getPath(), item.getUrl(), "", + Symbol.get(new SymbolRequest.Builder().withRaw(symbolName).build()))); + } rsp.serveExposedBean(req, r, Flavor.JSON); } @@ -259,19 +268,35 @@ public static class Item { private final String url; + public final String icon; + + public final String iconXml; + public Item(String name) { - this(name, null); + this(name, null, null, null); } - public Item(String name, String url) { + public Item(String name, String url, String icon, String iconXml) { this.name = name; this.url = url; + this.icon = icon; + this.iconXml = iconXml; } @Exported public String getUrl() { return url; } + + @Exported + public String getIcon() { + return icon; + } + + @Exported + public String getIconXml() { + return iconXml; + } } private enum Mode { diff --git a/core/src/main/java/hudson/search/SearchItem.java b/core/src/main/java/hudson/search/SearchItem.java index 02b2921b9741..e64efb0257ed 100644 --- a/core/src/main/java/hudson/search/SearchItem.java +++ b/core/src/main/java/hudson/search/SearchItem.java @@ -25,6 +25,7 @@ package hudson.search; import hudson.model.Build; +import org.jenkins.ui.icon.IconSpec; /** * Represents an item reachable from {@link SearchIndex}. @@ -54,6 +55,14 @@ public interface SearchItem { String getSearchUrl(); + default String getSearchIcon() { + if (this instanceof IconSpec) { + return ((IconSpec) this).getIconClassName(); + } + + return "symbol-search"; + } + /** * Returns the {@link SearchIndex} to further search sub items inside this item. * diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index eee96b5bca05..516ed8a5565a 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -147,6 +147,7 @@ import hudson.scm.RepositoryBrowser; import hudson.scm.SCM; import hudson.search.CollectionSearchIndex; +import hudson.search.SearchIndex; import hudson.search.SearchIndexBuilder; import hudson.search.SearchItem; import hudson.security.ACL; @@ -2345,9 +2346,29 @@ public String getSearchUrl() { @Override public SearchIndexBuilder makeSearchIndex() { SearchIndexBuilder builder = super.makeSearchIndex(); - if (hasPermission(ADMINISTER)) { - builder.add("manage", Messages.ManageJenkinsAction_DisplayName()); - } + + this.actions.stream().filter(e -> e.getIconFileName() != null).forEach(action -> builder.add(new SearchItem() { + @Override + public String getSearchName() { + return action.getDisplayName(); + } + + @Override + public String getSearchUrl() { + return action.getUrlName(); + } + + @Override + public String getSearchIcon() { + return action.getIconFileName(); + } + + @Override + public SearchIndex getSearchIndex() { + return SearchIndex.EMPTY; + } + })); + builder.add(new CollectionSearchIndex() { @Override protected SearchItem get(String key) { return getItemByFullName(key, TopLevelItem.class); } diff --git a/src/main/js/components/command-palette/datasources.js b/src/main/js/components/command-palette/datasources.js index 8357fca3feaf..20a380eb9157 100644 --- a/src/main/js/components/command-palette/datasources.js +++ b/src/main/js/components/command-palette/datasources.js @@ -1,6 +1,5 @@ import { LinkResult } from "./models"; import Search from "@/api/search"; -import * as Symbols from "./symbols"; export const JenkinsSearchSource = { execute(query) { @@ -18,7 +17,7 @@ export const JenkinsSearchSource = { rsp.json().then((data) => { return data["suggestions"].slice().map((e) => LinkResult({ - icon: Symbols.SEARCH, + icon: e.iconXml, label: e.name, url: correctAddress(e.url), }), diff --git a/src/main/js/components/command-palette/index.js b/src/main/js/components/command-palette/index.js index dfb3c6eb7efe..5ece1106aed0 100644 --- a/src/main/js/components/command-palette/index.js +++ b/src/main/js/components/command-palette/index.js @@ -94,18 +94,23 @@ function init() { } searchResultsContainer.style.height = searchResults.offsetHeight + "px"; + debouncedSpinner.cancel(); commandPaletteSearchBarContainer.classList.remove( "jenkins-search--loading", ); }); } + const debouncedSpinner = debounce(() => { + commandPaletteSearchBarContainer.classList.add("jenkins-search--loading"); + }, 150); + const debouncedLoad = debounce(() => { renderResults(); }, 150); commandPaletteInput.addEventListener("input", () => { - commandPaletteSearchBarContainer.classList.add("jenkins-search--loading"); + debouncedSpinner(); debouncedLoad(); }); @@ -119,7 +124,16 @@ function init() { } function hideCommandPalette() { - commandPalette.close(); + commandPalette.setAttribute("closing", ""); + + commandPalette.addEventListener( + "animationend", + () => { + commandPalette.removeAttribute("closing"); + commandPalette.close(); + }, + { once: true }, + ); } function itemMouseEnter(item) { diff --git a/src/main/js/components/command-palette/symbols.js b/src/main/js/components/command-palette/symbols.js index f1d7b63c4b2c..14fc815519da 100644 --- a/src/main/js/components/command-palette/symbols.js +++ b/src/main/js/components/command-palette/symbols.js @@ -1,3 +1,2 @@ export const EXTERNAL_LINK = ``; export const HELP = ``; -export const SEARCH = `Search`; diff --git a/src/main/scss/components/_command-palette.scss b/src/main/scss/components/_command-palette.scss index 6c3b16517e3a..ebd634cb9ce6 100644 --- a/src/main/scss/components/_command-palette.scss +++ b/src/main/scss/components/_command-palette.scss @@ -1,13 +1,5 @@ @use "../abstracts/mixins"; -.jenkins-command-palette__dialog { - &::backdrop { - background: var(--command-palette-backdrop-background); - backdrop-filter: contrast(0.7) brightness(0.9) saturate(1.25) blur(3px); - animation: jenkins-modal-backdrop-animate-in 0.3s; - } -} - .jenkins-command-palette__dialog { background: none; border: none; @@ -17,6 +9,41 @@ max-width: 100vw !important; margin: 0 !important; padding: 0 !important; + user-select: none; + + &::backdrop { + background: var(--command-palette-backdrop-background); + backdrop-filter: contrast(0.7) brightness(0.9) saturate(1.25) blur(3px); + animation: jenkins-dialog-backdrop-animate-in 0.1s linear; + } + + &[open] { + animation: command-palette-animate-in 0.1s cubic-bezier(0, 0.68, 0.5, 1.5); + } + + &[closing] { + animation: command-palette-animate-out 0.1s linear; + + &::backdrop { + animation: jenkins-dialog-backdrop-animate-out 0.1s linear; + } + } +} + +@keyframes command-palette-animate-in { + from { + translate: 0 4px; + scale: 98.5%; + opacity: 0; + transform: rotateX(30deg); + } +} + +@keyframes command-palette-animate-out { + to { + scale: 98.5%; + opacity: 0; + } } .jenkins-command-palette__wrapper { @@ -40,9 +67,7 @@ --search-bar-height: 3rem !important; background: transparent; - box-shadow: - 0 0 0 20px transparent, - var(--command-palette-inset-shadow); + box-shadow: var(--command-palette-inset-shadow); margin-bottom: 1.5rem; border-radius: 1rem; transition: var(--standard-transition); @@ -71,7 +96,8 @@ border-radius: 1rem; backdrop-filter: var(--command-palette-results-backdrop-filter); box-shadow: var(--command-palette-inset-shadow); - height: 0; + // If set to 0, Safari won't always show the backdrop-filter + height: 1px; transition: height var(--standard-transition); overflow: hidden; will-change: height; @@ -83,8 +109,8 @@ padding: 0.5rem; &__heading { - font-weight: 600; - font-size: 0.85rem; + font-weight: 500; + font-size: 0.875rem; margin: 0; padding: 0.75rem 0.75rem 0.625rem; color: var(--text-color-secondary); @@ -113,8 +139,7 @@ justify-content: flex-start; background: transparent; padding: 0.75rem; - border-radius: 0.66rem; - font-weight: 600; + border-radius: 0.5rem; cursor: pointer; color: var(--text-color) !important; transition: var(--standard-transition); @@ -132,17 +157,17 @@ display: flex; align-items: center; justify-content: center; - width: 1.4rem; - height: 1.4rem; - margin-right: 12.5px; + width: 1.375rem; + height: 1.375rem; + margin-right: 0.75rem; overflow: hidden; pointer-events: none; color: var(--text-color); svg, img { - width: 1.2rem; - height: 1.2rem; + width: 1.25rem; + height: 1.25rem; } } @@ -163,10 +188,10 @@ } &__info { - font-weight: 600; - font-size: 0.85rem; + font-size: 0.875rem; margin: 0; - padding: 12.5px 12.5px 10px; + padding: 0 14px; + line-height: 46px; color: var(--text-color); span { diff --git a/war/src/main/resources/images/symbols/jobs.svg b/war/src/main/resources/images/symbols/jobs.svg new file mode 100644 index 000000000000..b17b227c6401 --- /dev/null +++ b/war/src/main/resources/images/symbols/jobs.svg @@ -0,0 +1,5 @@ + + + + + From 24837ea667183f189ee0ab73e86cb8cda58c8fe2 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue, 10 Dec 2024 21:59:03 +0000 Subject: [PATCH 03/12] Update Search.java --- core/src/main/java/hudson/search/Search.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index a3c76a7fc9d5..853c8ea85177 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -160,14 +160,19 @@ public void doSuggestOpenSearch(StaplerRequest2 req, StaplerResponse2 rsp, @Quer public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter String query) throws IOException, ServletException { Result r = new Result(); for (SuggestedItem item : getSuggestions(req, query)) { - String symbolName = item.item.getSearchIcon(); - - if (symbolName == null || !symbolName.startsWith("symbol-")) { - symbolName = "symbol-search"; + String icon = item.item.getSearchIcon(); + String iconXml = null; + + if (icon == null) { + iconXml = Symbol.get(new SymbolRequest.Builder().withRaw("symbol-search").build()); + } else if (icon.startsWith("symbol-")) { + icon = null; + iconXml = Symbol.get(new SymbolRequest.Builder().withRaw(icon).build()); + } else { + icon = item.item.getSearchIcon(); } - r.suggestions.add(new Item(item.getPath(), item.getUrl(), "", - Symbol.get(new SymbolRequest.Builder().withRaw(symbolName).build()))); + r.suggestions.add(new Item(item.getPath(), item.getUrl(), icon, iconXml)); } rsp.serveExposedBean(req, r, Flavor.JSON); } From a9aadbab3068e2f456866523f94ff88fa9052184 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:25:40 +0000 Subject: [PATCH 04/12] Revert "Update Search.java" This reverts commit 24837ea667183f189ee0ab73e86cb8cda58c8fe2. --- core/src/main/java/hudson/search/Search.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index 853c8ea85177..a3c76a7fc9d5 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -160,19 +160,14 @@ public void doSuggestOpenSearch(StaplerRequest2 req, StaplerResponse2 rsp, @Quer public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter String query) throws IOException, ServletException { Result r = new Result(); for (SuggestedItem item : getSuggestions(req, query)) { - String icon = item.item.getSearchIcon(); - String iconXml = null; - - if (icon == null) { - iconXml = Symbol.get(new SymbolRequest.Builder().withRaw("symbol-search").build()); - } else if (icon.startsWith("symbol-")) { - icon = null; - iconXml = Symbol.get(new SymbolRequest.Builder().withRaw(icon).build()); - } else { - icon = item.item.getSearchIcon(); + String symbolName = item.item.getSearchIcon(); + + if (symbolName == null || !symbolName.startsWith("symbol-")) { + symbolName = "symbol-search"; } - r.suggestions.add(new Item(item.getPath(), item.getUrl(), icon, iconXml)); + r.suggestions.add(new Item(item.getPath(), item.getUrl(), "", + Symbol.get(new SymbolRequest.Builder().withRaw(symbolName).build()))); } rsp.serveExposedBean(req, r, Flavor.JSON); } From ea67d6a554417f3a976918b242b78ee3a816b2a9 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:26:16 +0000 Subject: [PATCH 05/12] Update Search.java --- core/src/main/java/hudson/search/Search.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index a3c76a7fc9d5..54813d1f6695 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -166,7 +166,7 @@ public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter symbolName = "symbol-search"; } - r.suggestions.add(new Item(item.getPath(), item.getUrl(), "", + r.suggestions.add(new Item(item.getPath(), item.getUrl(), Symbol.get(new SymbolRequest.Builder().withRaw(symbolName).build()))); } rsp.serveExposedBean(req, r, Flavor.JSON); @@ -268,18 +268,15 @@ public static class Item { private final String url; - public final String icon; - public final String iconXml; public Item(String name) { - this(name, null, null, null); + this(name, null, null); } - public Item(String name, String url, String icon, String iconXml) { + public Item(String name, String url, String iconXml) { this.name = name; this.url = url; - this.icon = icon; this.iconXml = iconXml; } @@ -288,11 +285,6 @@ public String getUrl() { return url; } - @Exported - public String getIcon() { - return icon; - } - @Exported public String getIconXml() { return iconXml; From 436a02b9d307183e6cdedca66ec565f639896474 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Wed, 11 Dec 2024 16:35:24 +0000 Subject: [PATCH 06/12] Add support for images --- core/src/main/java/hudson/search/Search.java | 36 +++++++++++++++---- .../components/command-palette/datasources.js | 1 + .../js/components/command-palette/index.js | 1 + .../js/components/command-palette/models.js | 6 ++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index 54813d1f6695..1edbfdb5dc3f 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -58,6 +58,7 @@ import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.StaplerResponse2; import org.kohsuke.stapler.export.DataWriter; +import org.kohsuke.stapler.export.ExportConfig; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.export.Flavor; @@ -159,17 +160,23 @@ public void doSuggestOpenSearch(StaplerRequest2 req, StaplerResponse2 rsp, @Quer */ public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter String query) throws IOException, ServletException { Result r = new Result(); - for (SuggestedItem item : getSuggestions(req, query)) { - String symbolName = item.item.getSearchIcon(); + for (SuggestedItem curItem : getSuggestions(req, query)) { + String iconName = curItem.item.getSearchIcon(); - if (symbolName == null || !symbolName.startsWith("symbol-")) { - symbolName = "symbol-search"; + if (iconName == null || + (!iconName.startsWith("symbol-") && !iconName.startsWith("http")) + ) { + iconName = "symbol-search"; } - r.suggestions.add(new Item(item.getPath(), item.getUrl(), - Symbol.get(new SymbolRequest.Builder().withRaw(symbolName).build()))); + if (iconName.startsWith("symbol")) { + r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(), + Symbol.get(new SymbolRequest.Builder().withRaw(iconName).build()))); + } else { + r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(), iconName, "image")); + } } - rsp.serveExposedBean(req, r, Flavor.JSON); + rsp.serveExposedBean(req, r, new ExportConfig()); } /** @@ -268,6 +275,8 @@ public static class Item { private final String url; + private final String type; + public final String iconXml; public Item(String name) { @@ -278,6 +287,14 @@ public Item(String name, String url, String iconXml) { this.name = name; this.url = url; this.iconXml = iconXml; + this.type = "symbol"; + } + + public Item(String name, String url, String iconXml, String type) { + this.name = name; + this.url = url; + this.iconXml = iconXml; + this.type = type; } @Exported @@ -289,6 +306,11 @@ public String getUrl() { public String getIconXml() { return iconXml; } + + @Exported + public String getType() { + return type; + } } private enum Mode { diff --git a/src/main/js/components/command-palette/datasources.js b/src/main/js/components/command-palette/datasources.js index 20a380eb9157..c1666101d410 100644 --- a/src/main/js/components/command-palette/datasources.js +++ b/src/main/js/components/command-palette/datasources.js @@ -18,6 +18,7 @@ export const JenkinsSearchSource = { return data["suggestions"].slice().map((e) => LinkResult({ icon: e.iconXml, + type: e.type, label: e.name, url: correctAddress(e.url), }), diff --git a/src/main/js/components/command-palette/index.js b/src/main/js/components/command-palette/index.js index 5ece1106aed0..8530ae3d4a30 100644 --- a/src/main/js/components/command-palette/index.js +++ b/src/main/js/components/command-palette/index.js @@ -59,6 +59,7 @@ function init() { results = Promise.all([ LinkResult({ icon: Symbols.HELP, + type: "symbol", label: i18n.dataset.getHelp, url: headerCommandPaletteButton.dataset.searchHelpUrl, isExternal: true, diff --git a/src/main/js/components/command-palette/models.js b/src/main/js/components/command-palette/models.js index 8159ce68c9a2..b3b06f22714c 100644 --- a/src/main/js/components/command-palette/models.js +++ b/src/main/js/components/command-palette/models.js @@ -5,6 +5,7 @@ import { xmlEscape } from "@/util/security"; * @param {Object} params * @param {string} params.icon * @param {string} params.label + * @param {string} params.type * @param {string} params.url * @param {boolean | undefined} params.isExternal */ @@ -16,9 +17,8 @@ export function LinkResult(params) { return ` -
${ - params.icon - }
+ ${params.type === "image" ? `${xmlEscape(params.label)}` : ""} + ${params.type !== "image" ? `
${params.icon}
` : ""} ${xmlEscape(params.label)} ${params.isExternal ? Symbols.EXTERNAL_LINK : ""}
`; From 26738449cd62ea3c007503c0bebff4872b1fc14b Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:03:29 +0000 Subject: [PATCH 07/12] Implement IconSpec in IComputer --- core/src/main/java/hudson/model/Computer.java | 5 ----- core/src/main/java/jenkins/model/IComputer.java | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 383361ee9a85..0c525dfabe84 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1109,11 +1109,6 @@ public String getSearchUrl() { return getUrl(); } - @Override - public String getSearchIcon() { - return this.getIconClassName(); - } - /** * {@link RetentionStrategy} associated with this computer. * diff --git a/core/src/main/java/jenkins/model/IComputer.java b/core/src/main/java/jenkins/model/IComputer.java index 37fe0af98535..e00ee78bd30f 100644 --- a/core/src/main/java/jenkins/model/IComputer.java +++ b/core/src/main/java/jenkins/model/IComputer.java @@ -35,6 +35,7 @@ import jenkins.agents.IOfflineCause; import org.jenkins.ui.icon.Icon; import org.jenkins.ui.icon.IconSet; +import org.jenkins.ui.icon.IconSpec; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.Beta; @@ -44,7 +45,7 @@ * @since 2.480 */ @Restricted(Beta.class) -public interface IComputer extends AccessControlled { +public interface IComputer extends AccessControlled, IconSpec { /** * Returns {@link Node#getNodeName() the name of the node}. */ From d6868c970a06a17633cf7d145beef28598730a0b Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Wed, 11 Dec 2024 21:11:41 +0000 Subject: [PATCH 08/12] Reword javadoc --- core/src/main/java/org/jenkins/ui/icon/IconSpec.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/jenkins/ui/icon/IconSpec.java b/core/src/main/java/org/jenkins/ui/icon/IconSpec.java index c44e577e1ce0..f1bf535b6ca6 100644 --- a/core/src/main/java/org/jenkins/ui/icon/IconSpec.java +++ b/core/src/main/java/org/jenkins/ui/icon/IconSpec.java @@ -27,8 +27,7 @@ /** * Icon Specification. *
- * Plugin extension points that implement/extend Action/ManagementLink should - * also implement this interface. + * If your class provides an icon spec you should implement this interface. * * @author tom.fennelly@gmail.com * @since 2.0 From 809d2e6120b36093c60ab88e2794d14fb3aaa5ea Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:37:47 +0000 Subject: [PATCH 09/12] Make iconXml private, rename to icon --- core/src/main/java/hudson/search/Search.java | 14 +++++++------- .../js/components/command-palette/datasources.js | 2 +- src/main/js/components/command-palette/models.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index 1edbfdb5dc3f..97d15b65c2df 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -277,23 +277,23 @@ public static class Item { private final String type; - public final String iconXml; + private final String icon; public Item(String name) { this(name, null, null); } - public Item(String name, String url, String iconXml) { + public Item(String name, String url, String icon) { this.name = name; this.url = url; - this.iconXml = iconXml; + this.icon = icon; this.type = "symbol"; } - public Item(String name, String url, String iconXml, String type) { + public Item(String name, String url, String icon, String type) { this.name = name; this.url = url; - this.iconXml = iconXml; + this.icon = icon; this.type = type; } @@ -303,8 +303,8 @@ public String getUrl() { } @Exported - public String getIconXml() { - return iconXml; + public String getIcon() { + return icon; } @Exported diff --git a/src/main/js/components/command-palette/datasources.js b/src/main/js/components/command-palette/datasources.js index c1666101d410..58dd5389d540 100644 --- a/src/main/js/components/command-palette/datasources.js +++ b/src/main/js/components/command-palette/datasources.js @@ -17,7 +17,7 @@ export const JenkinsSearchSource = { rsp.json().then((data) => { return data["suggestions"].slice().map((e) => LinkResult({ - icon: e.iconXml, + icon: e.icon, type: e.type, label: e.name, url: correctAddress(e.url), diff --git a/src/main/js/components/command-palette/models.js b/src/main/js/components/command-palette/models.js index b3b06f22714c..0c6a733e0d0a 100644 --- a/src/main/js/components/command-palette/models.js +++ b/src/main/js/components/command-palette/models.js @@ -5,7 +5,7 @@ import { xmlEscape } from "@/util/security"; * @param {Object} params * @param {string} params.icon * @param {string} params.label - * @param {string} params.type + * @param {'symbol' | 'image'} params.type * @param {string} params.url * @param {boolean | undefined} params.isExternal */ From e65ce203ba08fd648c5a8668429f60eed527f08d Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:27:27 +0000 Subject: [PATCH 10/12] Update AbstractProjectTest.java --- test/src/test/java/hudson/model/AbstractProjectTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index 04db7ea8f4fb..b8a9d8e1ea6a 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -664,6 +664,8 @@ private void testAutoCompleteResponse(JSONObject responseBody, String... project JSONObject o = new JSONObject(); o.put("name", p); o.put("url", JSONObject.fromObject(null)); + o.put("icon", JSONObject.fromObject(null)); + o.put("type", "symbol"); expected.add(o); } assertThat(suggestions.containsAll(expected), is(true)); From b3347e2dbdd293808ac067f00939637c61a988a5 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat, 14 Dec 2024 08:51:49 +0000 Subject: [PATCH 11/12] Trigger Build From b3d636e29da2a4ede878c88c84823cd6655210a9 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sun, 15 Dec 2024 08:53:20 +0000 Subject: [PATCH 12/12] Trigger Build