Skip to content

Commit

Permalink
refresh hetero-list UI
Browse files Browse the repository at this point in the history
use new button style
replace the YUI menu with a tippy menu
  • Loading branch information
mawinter69 committed Aug 6, 2023
1 parent a6ca845 commit 79aa919
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 16 deletions.
9 changes: 5 additions & 4 deletions core/src/main/resources/lib/form/hetero-list.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ THE SOFTWARE.
</d:tag>
</d:taglib>

<st:adjunct includes="lib.form.hetero-list.hetero-list"/>
<!-- <st:adjunct includes="lib.form.hetero-list.hetero-list"/> -->

<j:set var="targetType" value="${attrs.targetType?:it.class}"/>
<div class="jenkins-form-item hetero-list-container ${hasHeader?'with-drag-drop':''} ${attrs.oneEach?'one-each':''} ${attrs.honorOrder?'honor-order':''}">
Expand Down Expand Up @@ -155,9 +155,10 @@ THE SOFTWARE.
</div>

<j:if test="${!readOnlyMode}">
<div>
<input type="button" value="${attrs.addCaption?:'%Add'}" class="hetero-list-add" menualign="${attrs.menuAlign}" suffix="${attrs.name}"/>
</div>
<button type="button" class="jenkins-button hetero-list-add" menualign="${attrs.menuAlign}" suffix="${attrs.name}">
${attrs.addCaption?:'%Add'}
<l:icon src="symbol-chevron-down"/>
</button>
</j:if>
</div>
</j:jelly>
252 changes: 252 additions & 0 deletions war/src/main/js/components/dropdowns/hetero-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import behaviorShim from "@/util/behavior-shim";
import Templates from "@/components/dropdowns/templates";
import Utils from "@/components/dropdowns/utils";
import tippy from "tippy.js";

function init() {
generateButtons();
//generateHandles();
}

function generateHandles() {
behaviorShim.specify("DIV.dd-handle", "hetero-list", -100, function (e) {
e.addEventListener("mouseover", function () {
this.closest(".repeated-chunk").classList.add("hover");
});
e.addEventListener("mouseout", function () {
this.closest(".repeated-chunk").classList.remove("hover");
});
});
}

function generateButtons() {
behaviorShim.specify(
"DIV.hetero-list-container",
"hetero-list-new",
-100,
function (e) {
if (isInsideRemovable(e)) {
return;
}

let btn = Array.from(e.querySelectorAll("BUTTON.hetero-list-add")).pop();
if (!btn) {
return;
}
let prototypes = e.lastElementChild;
while (!prototypes.classList.contains("prototypes")) {
prototypes = prototypes.previousElementSibling;
}
let insertionPoint = prototypes.previousElementSibling; // this is where the new item is inserted.

let templates = [];
let children = prototypes.children;
for (let i = 0; i < children.length; i++) {
let n = children[i];
let name = n.getAttribute("name");
let tooltip = n.getAttribute("tooltip");
let descriptorId = n.getAttribute("descriptorId");
let title = n.getAttribute("title");
templates.push({
html: n.innerHTML,
name: name,
tooltip: tooltip,
descriptorId: descriptorId,
title: title,
});
}
prototypes.remove();
let withDragDrop = registerSortableDragDrop(e);

function insert(instance, index) {
var t = templates[parseInt(index)];
var nc = document.createElement("div");
nc.className = "repeated-chunk";
nc.setAttribute("name", t.name);
nc.setAttribute("descriptorId", t.descriptorId);
nc.innerHTML = t.html;
nc.style.opacity = "0";

instance.hide();

renderOnDemand(
nc.querySelector("div.config-page"),
function () {
function findInsertionPoint() {
// given the element to be inserted 'prospect',
// and the array of existing items 'current',
// and preferred ordering function, return the position in the array
// the prospect should be inserted.
// (for example 0 if it should be the first item)
function findBestPosition(prospect, current, order) {
function desirability(pos) {
var count = 0;
for (var i = 0; i < current.length; i++) {
if (i < pos == order(current[i]) <= order(prospect)) {
count++;
}
}
return count;
}

var bestScore = -1;
var bestPos = 0;
for (var i = 0; i <= current.length; i++) {
var d = desirability(i);
if (bestScore <= d) {
// prefer to insert them toward the end
bestScore = d;
bestPos = i;
}
}
return bestPos;
}

var current = Array.from(e.children).filter(function (e) {
return e.matches("DIV.repeated-chunk");
});

function o(did) {
if (did instanceof Element) {
did = did.getAttribute("descriptorId");
}
for (var i = 0; i < templates.length; i++) {
if (templates[i].descriptorId == did) {
return i;
}
}
return 0; // can't happen
}

var bestPos = findBestPosition(t.descriptorId, current, o);
if (bestPos < current.length) {
return current[bestPos];
} else {
return insertionPoint;
}
}
var referenceNode = e.classList.contains("honor-order")
? findInsertionPoint()
: insertionPoint;
referenceNode.parentNode.insertBefore(nc, referenceNode);

// Initialize drag & drop for this component
if (withDragDrop) {
registerSortableDragDrop(nc);
}

new YAHOO.util.Anim(
nc,
{
opacity: { to: 1 },
},
0.2,
YAHOO.util.Easing.easeIn,
).animate();

Behaviour.applySubtree(nc, true);
ensureVisible(nc);
layoutUpdateCallback.call();
},
true,
);
}

function has(id) {
return (
e.querySelector('DIV.repeated-chunk[descriptorId="' + id + '"]') !=
null
);
}

let oneEach = e.classList.contains("one-each");

generateDropDown(btn, (instance) => {
let menuItems = [];
for (let i = 0; i < templates.length; i++) {
let n = templates[i];
let disabled = oneEach && has(n.descriptorId);
let type = disabled ? "DISABLED" : "button";
let item = {
label: n.title,
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
insert(instance, i);
},
type: type,
};
menuItems.push(item);
}
let menu = Utils.generateDropdownItems(menuItems, true);
createFilter(menu);
instance.setContent(menu);
});
},
);
}

function createFilter(menu) {
const filterInput = document.createElement("input");
filterInput.classList.add("jenkins-input");
filterInput.setAttribute("placeholder", "Filter");
filterInput.setAttribute("spellcheck", "false");
filterInput.setAttribute("type", "search");

filterInput.addEventListener("input", (event) =>
applyFilterKeyword(menu, event.currentTarget),
);
filterInput.addEventListener("click", (event) =>
event.stopPropagation(),
);
filterInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
}
});

const filterContainer = document.createElement("div");
filterContainer.appendChild(filterInput);
menu.insertBefore(filterContainer, menu.firstChild);
}

function applyFilterKeyword(menu, filterInput) {
const filterKeyword = (filterInput.value || "").toLowerCase();
let items = menu.querySelectorAll(".jenkins-dropdown__item, .jenkins-dropdown__disabled")
for (let item of items) {
let match = item.innerText.toLowerCase().includes(filterKeyword);
item.style.display = match ? "inline-flex" : "NONE";
}
}

function generateDropDown(button, callback) {
tippy(
button,
Object.assign({}, Templates.dropdown(), {
appendTo: undefined,
offset: [0, 5],
onCreate(instance) {
if (instance.loaded) {
return;
}
instance.popper.addEventListener("click", () => {
instance.hide();
});
instance.popper.addEventListener("keydown", () => {
if (event.key === "Escape") {
instance.hide();
}
});
},
onShow(instance) {
callback(instance);
button.dataset.expanded = "true";
},
onHide(instance) {
button.dataset.expanded = "false";
},
}),
);
}

export default { init };
2 changes: 2 additions & 0 deletions war/src/main/js/components/dropdowns/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Jumplists from "@/components/dropdowns/jumplists";
import InpageJumplist from "@/components/dropdowns/inpage-jumplist";
import OverflowButton from "@/components/dropdowns/overflow-button";
import HeteroLists from "@/components/dropdowns/hetero-list";

function init() {
Jumplists.init();
InpageJumplist.init();
OverflowButton.init();
HeteroLists.init();
}

export default { init };
9 changes: 8 additions & 1 deletion war/src/main/js/components/dropdowns/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function menuItem(options) {
`);

if (options.onClick) {
item.addEventListener("click", () => options.onClick());
item.addEventListener("click", (event) => options.onClick(event));
}

return item;
Expand All @@ -94,10 +94,17 @@ function placeholder(label) {
);
}

function disabled(label) {
return createElementFromHtml(
`<p class="jenkins-dropdown__disabled">${label}</p>`,
);
}

export default {
dropdown,
menuItem,
heading,
separator,
placeholder,
disabled,
};
9 changes: 8 additions & 1 deletion war/src/main/js/components/dropdowns/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ function generateDropdown(element, callback) {
/*
* Generates the contents for the dropdown
*/
function generateDropdownItems(items) {
function generateDropdownItems(items, compact) {
const menuItems = document.createElement("div");
menuItems.classList.add("jenkins-dropdown");
if (compact === true) {
menuItems.classList.add("jenkins-dropdown--compact");
}

items
.map((item) => {
Expand All @@ -52,6 +55,10 @@ function generateDropdownItems(items) {
return Templates.separator();
}

if (item.type === "DISABLED") {
return Templates.disabled(item.label);
}

const menuItem = Templates.menuItem(item);

if (item.subMenu != null) {
Expand Down
Loading

0 comments on commit 79aa919

Please sign in to comment.