Expose default option in SingleItemSelector

- Tweak flow framework so that single item selector
  can be configured with a default item which is then
  "exposed" automatically so that user can just hit enter.
- Fixes #414
This commit is contained in:
Janne Valkealahti
2022-05-13 15:31:29 +01:00
parent 8477a5ac4c
commit d49429975d
6 changed files with 172 additions and 43 deletions

View File

@@ -41,6 +41,7 @@ public abstract class BaseSingleItemSelector extends BaseInput<SingleItemSelecto
private String resultValue;
private ResultMode resultMode;
private Map<String, String> selectItems = new HashMap<>();
private String defaultSelect;
private Comparator<SelectorItem<String>> comparator;
private Function<SingleItemSelectorContext<String, SelectorItem<String>>, List<AttributedString>> renderer;
private Integer maxItems;
@@ -84,6 +85,12 @@ public abstract class BaseSingleItemSelector extends BaseInput<SingleItemSelecto
return this;
}
@Override
public SingleItemSelectorSpec defaultSelect(String name) {
this.defaultSelect = name;
return this;
}
@Override
public SingleItemSelectorSpec sort(Comparator<SelectorItem<String>> comparator) {
this.comparator = comparator;
@@ -160,6 +167,10 @@ public abstract class BaseSingleItemSelector extends BaseInput<SingleItemSelecto
return selectItems;
}
public String getDefaultSelect() {
return defaultSelect;
}
public Comparator<SelectorItem<String>> getComparator() {
return comparator;
}

View File

@@ -555,8 +555,20 @@ public interface ComponentFlow {
List<SelectorItem<String>> selectorItems = input.getSelectItems().entrySet().stream()
.map(e -> SelectorItem.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());
// setup possible item for initial expose
String defaultSelect = input.getDefaultSelect();
Stream<SelectorItem<String>> defaultCheckStream = StringUtils.hasText(defaultSelect)
? selectorItems.stream()
: Stream.empty();
SelectorItem<String> defaultExpose = defaultCheckStream
.filter(si -> ObjectUtils.nullSafeEquals(defaultSelect, si.getName()))
.findFirst()
.orElse(null);
SingleItemSelector<String, SelectorItem<String>> selector = new SingleItemSelector<>(terminal,
selectorItems, input.getName(), input.getComparator());
selector.setDefaultExpose(defaultExpose);
Function<ComponentContext<?>, ComponentContext<?>> operation = (context) -> {
if (input.getResultMode() == ResultMode.ACCEPT && input.isStoreResult()
&& StringUtils.hasText(input.getResultValue())) {

View File

@@ -76,6 +76,14 @@ public interface SingleItemSelectorSpec extends BaseInputSpec<SingleItemSelector
*/
SingleItemSelectorSpec selectItems(Map<String, String> selectItems);
/**
* Automatically selects and exposes a given item.
*
* @param name the name
* @return a builder
*/
SingleItemSelectorSpec defaultSelect(String name);
/**
* Sets a {@link Comparator} for sorting items.
*

View File

@@ -31,6 +31,7 @@ import org.springframework.shell.component.context.BaseComponentContext;
import org.springframework.shell.component.context.ComponentContext;
import org.springframework.shell.component.support.AbstractSelectorComponent.SelectorComponentContext;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.jline.keymap.KeyMap.ctrl;
@@ -54,6 +55,8 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
private boolean stale = false;
private AtomicInteger start = new AtomicInteger(0);
private AtomicInteger pos = new AtomicInteger(0);
private I defaultExpose;
private boolean expose = false;
public AbstractSelectorComponent(Terminal terminal, String name, List<I> items, boolean exitSelects,
Comparator<I> comparator) {
@@ -95,6 +98,18 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
return itemMapper;
}
/**
* Sets default expose item when component start.
*
* @param defaultExpose the default item
*/
public void setDefaultExpose(I defaultExpose) {
this.defaultExpose = defaultExpose;
if (defaultExpose != null) {
expose = true;
}
}
/**
* Gets items.
*
@@ -120,6 +135,7 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
@Override
protected C runInternal(C context) {
C thisContext = getThisContext(context);
initialExpose(thisContext);
ItemStateViewProjection buildItemStateView = buildItemStateView(start.get(), thisContext);
List<ItemState<I>> itemStateView = buildItemStateView.items;
thisContext.setItemStateView(itemStateView);
@@ -224,6 +240,33 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
return false;
}
private void initialExpose(C context) {
if (!expose) {
return;
}
expose = false;
List<ItemState<I>> itemStates = context.getItemStates();
if (itemStates == null) {
AtomicInteger index = new AtomicInteger(0);
itemStates = context.getItems().stream()
.sorted(comparator)
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled()))
.collect(Collectors.toList());
}
for (int i = 0; i < itemStates.size(); i++) {
if (ObjectUtils.nullSafeEquals(itemStates.get(i).getName(), defaultExpose.getName())) {
if (i < maxItems) {
this.pos.set(i);
}
else {
this.pos.set(maxItems - 1);
this.start.set(i - maxItems + 1);
}
break;
}
}
}
private ItemStateViewProjection buildItemStateView(int skip, SelectorComponentContext<T, I, ?> context) {
List<ItemState<I>> itemStates = context.getItemStates();
if (itemStates == null) {