Rework [stage]

This commit is contained in:
crschnick 2025-04-09 18:17:26 +00:00
parent c10c10dee5
commit ed87bbd0c5
36 changed files with 316 additions and 173 deletions

View file

@ -16,6 +16,7 @@ import javafx.scene.control.Button;
import javafx.scene.layout.Region;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
@ -67,6 +68,7 @@ public final class BrowserConnectionListComp extends SimpleComp {
var section = new StoreSectionMiniComp(
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(),
Set.of(),
this::filter,
filter,
category,

View file

@ -82,18 +82,20 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
var queueButtons = new VBox();
queueEntries.addListener((ListChangeListener<? super AppLayoutModel.QueueEntry>) c -> {
queueButtons.getChildren().clear();
for (int i = c.getList().size() - 1; i >= 0; i--) {
var item = c.getList().get(i);
var b = new IconButtonComp(item.getIcon(), () -> {
item.getAction().run();
queueEntries.remove(item);
});
b.tooltip(item.getName());
b.accessibleText(item.getName());
var stack = createStyle(null, b);
queueButtons.getChildren().add(stack.createRegion());
}
PlatformThread.runLaterIfNeeded(() -> {
queueButtons.getChildren().clear();
for (int i = c.getList().size() - 1; i >= 0; i--) {
var item = c.getList().get(i);
var b = new IconButtonComp(item.getIcon(), () -> {
item.getAction().run();
queueEntries.remove(item);
});
b.tooltip(item.getName());
b.accessibleText(item.getName());
var stack = createStyle(null, b);
queueButtons.getChildren().add(stack.createRegion());
}
});
});
vbox.getChildren().add(queueButtons);

View file

@ -35,6 +35,7 @@ import lombok.RequiredArgsConstructor;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@RequiredArgsConstructor
@ -103,6 +104,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
var section = new StoreSectionMiniComp(
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(),
Set.of(),
applicable,
filterText,
selectedCategory,

View file

@ -39,8 +39,6 @@ public class StoreEntryBatchSelectComp extends SimpleComp {
section.getShownChildren().getList().addListener((ListChangeListener<? super StoreSection>) c -> {
if (cb.isSelected()) {
StoreViewState.get().selectBatchMode(section);
} else {
StoreViewState.get().unselectBatchMode(section);
}
});

View file

@ -11,6 +11,8 @@ import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
@ -46,7 +48,8 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
l.apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
});
var actions = new ToolbarComp(createActions());
var busy = new SimpleBooleanProperty();
var actions = new ToolbarComp(createActions(busy));
var close = new IconButtonComp("mdi2c-close", () -> {
StoreViewState.get().getBatchMode().setValue(false);
});
@ -66,10 +69,11 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
bar.prefHeight(40);
bar.styleClass("bar");
bar.styleClass("store-entry-list-status-bar");
bar.disable(busy);
return bar.createRegion();
}
private ObservableList<Comp<?>> createActions() {
private ObservableList<Comp<?>> createActions(BooleanProperty busy) {
var l = new DerivedObservableList<ActionProvider>(FXCollections.observableArrayList(), true);
StoreViewState.get().getEffectiveBatchModeSelection().getList().addListener((ListChangeListener<
? super StoreEntryWrapper>)
@ -77,7 +81,7 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
l.setContent(getCompatibleActionProviders());
});
return l.<Comp<?>>mapped(actionProvider -> {
return buildButton(actionProvider);
return buildButton(actionProvider, busy);
})
.getList();
}
@ -115,32 +119,30 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
}
@SuppressWarnings("unchecked")
private <T extends DataStore> Comp<?> buildButton(ActionProvider p) {
private <T extends DataStore> Comp<?> buildButton(ActionProvider p, BooleanProperty busy) {
ActionProvider.BatchDataStoreCallSite<T> s =
(ActionProvider.BatchDataStoreCallSite<T>) p.getBatchDataStoreCallSite();
if (s == null) {
return Comp.empty();
}
List<DataStoreEntryRef<T>> childrenRefs =
StoreViewState.get().getEffectiveBatchModeSelection().getList().stream()
.map(storeEntryWrapper -> storeEntryWrapper.getEntry().<T>ref())
.toList();
var batchActions = s.getChildren(childrenRefs);
var childrenRefs = StoreViewState.get().getEffectiveBatchModeSelection()
.mapped(storeEntryWrapper -> storeEntryWrapper.getEntry().<T>ref());
var batchActions = s.getChildren(childrenRefs.getList());
var button = new ButtonComp(
s.getName(), new SimpleObjectProperty<>(new LabelGraphic.IconGraphic(s.getIcon())), () -> {
s.getName(), new SimpleObjectProperty<>(s.getIcon()), () -> {
if (batchActions.size() > 0) {
return;
}
runActions(s);
runActions(s, busy);
});
if (batchActions.size() > 0) {
button.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
var cm = ContextMenuHelper.create();
s.getChildren(childrenRefs).forEach(childProvider -> {
var menu = buildMenuItemForAction(childrenRefs, childProvider);
s.getChildren(childrenRefs.getList()).forEach(childProvider -> {
var menu = buildMenuItemForAction(childrenRefs.getList(), childProvider, busy);
cm.getItems().add(menu);
});
return cm;
@ -150,7 +152,7 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
}
@SuppressWarnings("unchecked")
private <T extends DataStore> MenuItem buildMenuItemForAction(List<DataStoreEntryRef<T>> batch, ActionProvider a) {
private <T extends DataStore> MenuItem buildMenuItemForAction(List<DataStoreEntryRef<T>> batch, ActionProvider a, BooleanProperty busy) {
ActionProvider.BatchDataStoreCallSite<T> s =
(ActionProvider.BatchDataStoreCallSite<T>) a.getBatchDataStoreCallSite();
var name = s.getName();
@ -159,19 +161,19 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
if (children.size() > 0) {
var menu = new Menu();
menu.textProperty().bind(name);
menu.setGraphic(new LabelGraphic.IconGraphic(icon).createGraphicNode());
menu.setGraphic(icon.createGraphicNode());
var items = children.stream()
.filter(actionProvider -> actionProvider.getBatchDataStoreCallSite() != null)
.map(c -> buildMenuItemForAction(batch, c))
.map(c -> buildMenuItemForAction(batch, c, busy))
.toList();
menu.getItems().addAll(items);
return menu;
} else {
var item = new MenuItem();
item.textProperty().bind(name);
item.setGraphic(new LabelGraphic.IconGraphic(icon).createGraphicNode());
item.setGraphic(icon.createGraphicNode());
item.setOnAction(event -> {
runActions(s);
runActions(s, busy);
event.consume();
if (event.getTarget() instanceof Menu m) {
m.getParentPopup().hide();
@ -182,14 +184,16 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
}
@SuppressWarnings("unchecked")
private <T extends DataStore> void runActions(ActionProvider.BatchDataStoreCallSite<?> s) {
private <T extends DataStore> void runActions(ActionProvider.BatchDataStoreCallSite<?> s, BooleanProperty busy) {
ThreadHelper.runFailableAsync(() -> {
var l = new ArrayList<>(
StoreViewState.get().getEffectiveBatchModeSelection().getList());
var mapped = l.stream().map(w -> w.getEntry().<T>ref()).toList();
var action = ((ActionProvider.BatchDataStoreCallSite<T>) s).createAction(mapped);
if (action != null) {
action.execute();
BooleanScope.executeExclusive(busy, () -> {
action.execute();
});
}
});
}

View file

@ -19,6 +19,7 @@ import lombok.Getter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
@ -107,6 +108,7 @@ public class StoreSection {
public static StoreSection createTopLevel(
DerivedObservableList<StoreEntryWrapper> all,
Set<StoreEntryWrapper> selected,
Predicate<StoreEntryWrapper> entryFilter,
ObservableValue<String> filterString,
ObservableValue<StoreCategoryWrapper> category,
@ -124,6 +126,7 @@ public class StoreSection {
storeEntryWrapper,
1,
all,
selected,
entryFilter,
filterString,
category,
@ -152,6 +155,7 @@ public class StoreSection {
StoreEntryWrapper e,
int depth,
DerivedObservableList<StoreEntryWrapper> all,
Set<StoreEntryWrapper> selected,
Predicate<StoreEntryWrapper> entryFilter,
ObservableValue<String> filterString,
ObservableValue<StoreCategoryWrapper> category,
@ -188,21 +192,23 @@ public class StoreSection {
var l = new ArrayList<>(parents);
l.add(e);
var cached = allChildren.mapped(c -> create(
l, c, depth + 1, all, entryFilter, filterString, category, visibilityObservable, updateObservable));
l, c, depth + 1, all, selected, entryFilter, filterString, category, visibilityObservable, updateObservable));
var ordered = sorted(cached, category, updateObservable);
var filtered = ordered.filtered(
section -> {
var isBatchSelected = selected.contains(section.getWrapper());
var matchesFilter = filterString == null
|| section.matchesFilter(filterString.getValue())
|| l.stream().anyMatch(p -> p.matchesFilter(filterString.getValue()));
if (!matchesFilter) {
if (!isBatchSelected && !matchesFilter) {
return false;
}
var hasFilter = filterString != null
&& filterString.getValue() != null
&& filterString.getValue().length() > 0;
if (!hasFilter) {
if (!isBatchSelected && !hasFilter) {
var showProvider = true;
try {
showProvider = section.getWrapper()
@ -217,7 +223,7 @@ public class StoreSection {
}
var matchesSelector = section.anyMatches(entryFilter);
if (!matchesSelector) {
if (!isBatchSelected && !matchesSelector) {
return false;
}

View file

@ -68,7 +68,7 @@ public class StoreViewState {
}
return true;
});
}, entriesListVisibilityObservable, entriesListUpdateObservable);
@Getter
private StoreSection currentTopLevelSection;
@ -88,7 +88,7 @@ public class StoreViewState {
INSTANCE.initSections();
INSTANCE.updateContent();
INSTANCE.initFilterListener();
INSTANCE.initBatchListener();
INSTANCE.initBatchListeners();
INSTANCE.initialized = true;
}
@ -152,9 +152,17 @@ public class StoreViewState {
}
private void initSections() {
// Faster selection check with a hash set
var set = new HashSet<>(batchModeSelection.getList());
batchModeSelection.getList().addListener((ListChangeListener<? super StoreEntryWrapper>) c -> {
set.clear();
set.addAll(c.getList());
});
try {
currentTopLevelSection = StoreSection.createTopLevel(
allEntries,
set,
storeEntryWrapper -> true,
filter,
activeCategory,
@ -186,10 +194,14 @@ public class StoreViewState {
});
}
private void initBatchListener() {
private void initBatchListeners() {
allEntries.getList().addListener((ListChangeListener<? super StoreEntryWrapper>) c -> {
batchModeSelection.getList().retainAll(c.getList());
});
batchMode.addListener((observable, oldValue, newValue) -> {
batchModeSelection.getList().clear();
});
}
private void initContent() {

View file

@ -203,7 +203,7 @@ public interface ActionProvider {
ObservableValue<String> getName();
String getIcon();
LabelGraphic getIcon();
Class<?> getApplicableClass();

View file

@ -21,6 +21,22 @@ public class ContainerStoreState extends ShellStoreState {
String containerState;
Boolean shellMissing;
public boolean isExited() {
if (containerState == null) {
return false;
}
return containerState.toLowerCase().contains("exited");
}
public boolean isRunning() {
if (containerState == null) {
return false;
}
return containerState.toLowerCase().contains("running") || containerState.toLowerCase().contains("up");
}
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
var n = (ContainerStoreState) newer;

View file

@ -31,13 +31,11 @@ public abstract class ScanProvider {
public class ScanOpportunity {
String nameKey;
boolean disabled;
boolean defaultSelected;
String licenseFeatureId;
public ScanOpportunity(String nameKey, boolean disabled, boolean defaultSelected) {
public ScanOpportunity(String nameKey, boolean disabled) {
this.nameKey = nameKey;
this.disabled = disabled;
this.defaultSelected = defaultSelected;
this.licenseFeatureId = null;
}

View file

@ -46,7 +46,7 @@ public class ErrorHandlerDialog {
var comp = new ErrorHandlerComp(event, () -> {
AppDialog.closeDialog(modal.get());
});
comp.prefWidth(500);
comp.prefWidth(event.getThrowable() != null ? 600 : 500);
var headerId = event.isTerminal() ? "terminalErrorOccured" : "errorOccured";
var errorModal = ModalOverlay.of(headerId, comp, new LabelGraphic.NodeGraphic(() -> {
var graphic = new FontIcon("mdomz-warning");
@ -58,7 +58,7 @@ public class ErrorHandlerDialog {
"stackTrace",
() -> {
var content =
new ErrorDetailsComp(event).prefWidth(600).prefHeight(750);
new ErrorDetailsComp(event).prefWidth(650).prefHeight(750);
var detailsModal = ModalOverlay.of("errorDetails", content);
detailsModal.show();
},

View file

@ -10,61 +10,86 @@ import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class CommandDialog {
public static void runAsyncAndShow(CommandControl cmd) {
public static void runAsyncAndShow(Map<String, CommandControl> cmds) {
ThreadHelper.runAsync(() -> {
run(cmd);
StringBuilder acc = new StringBuilder();
for (var e : cmds.entrySet()) {
String out;
try {
out = e.getValue().readStdoutOrThrow();
out = formatOutput(out);
} catch (ProcessOutputException ex) {
out = ex.getMessage();
} catch (Throwable t) {
out = ExceptionUtils.getStackTrace(t);
}
acc.append(e.getKey()).append(" (exit code ").append(e.getValue().getExitCode()).append("):\n").append(out).append("\n\n");
}
show(acc.toString());
});
}
private static void run(CommandControl cmd) {
String out;
try {
out = cmd.readStdoutOrThrow();
if (out.isEmpty()) {
out = "<empty>";
public static void runAsyncAndShow(CommandControl cmd) {
ThreadHelper.runAsync(() -> {
String out;
try {
out = cmd.readStdoutOrThrow();
out = formatOutput(out);
} catch (ProcessOutputException e) {
out = e.getMessage();
} catch (Throwable t) {
out = ExceptionUtils.getStackTrace(t);
}
show(out);
});
}
if (out.length() > 10000) {
var counter = new AtomicInteger();
var start = out.lines()
.filter(s -> {
counter.incrementAndGet();
return true;
})
.limit(100)
.collect(Collectors.joining("\n"));
var notShownLines = counter.get() - 100;
if (notShownLines > 0) {
out = start + "\n\n... " + notShownLines + " more lines";
} else {
out = start;
}
}
} catch (ProcessOutputException e) {
out = e.getMessage();
} catch (Throwable t) {
out = ExceptionUtils.getStackTrace(t);
}
String finalOut = out;
private static void show(String out) {
var modal = ModalOverlay.of(
"commandOutput",
Comp.of(() -> {
var text = new TextArea(finalOut);
var text = new TextArea(out);
text.setWrapText(true);
text.setEditable(false);
text.setPrefRowCount(
Math.max(8, (int) finalOut.lines().count()));
Math.max(8, (int) out.lines().count()));
var sp = new StackPane(text);
return sp;
})
.prefWidth(650));
modal.show();
}
private static String formatOutput(String out) {
if (out.isEmpty()) {
out = "<empty>";
}
if (out.length() > 10000) {
var counter = new AtomicInteger();
var start = out.lines()
.filter(s -> {
counter.incrementAndGet();
return true;
})
.limit(100)
.collect(Collectors.joining("\n"));
var notShownLines = counter.get() - 100;
if (notShownLines > 0) {
out = start + "\n\n... " + notShownLines + " more lines";
} else {
out = start;
}
}
return out;
}
}

View file

@ -2,6 +2,8 @@ package io.xpipe.app.util;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
@ -41,10 +43,17 @@ public class ScanDialog {
public static void showMulti(List<DataStoreEntryRef<ShellStore>> entries, ScanDialogAction action) {
var comp = new ScanMultiDialogComp(entries, action);
var modal = ModalOverlay.of("scanAlertTitle", comp);
var queueEntry = new AppLayoutModel.QueueEntry(AppI18n.observable("scanConnections"), new LabelGraphic.IconGraphic("mdi2l-layers-plus"), () -> {});
var button = new ModalButton(
"ok",
() -> {
comp.finish();
modal.hide();
AppLayoutModel.get().getQueueEntries().add(queueEntry);
ThreadHelper.runAsync(() -> {
comp.finish();
modal.hide();
AppLayoutModel.get().getQueueEntries().remove(queueEntry);
});
},
false,
true);

View file

@ -38,10 +38,16 @@ public interface ScanDialogAction {
sc.start();
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
if (operation != null) {
if (!operation.isDisabled() && operation.isDefaultSelected()) {
if (!operation.isDisabled()) {
selected.removeIf(o -> o.getProvider().equals(operation.getProvider()) && o.isDisabled());
all.removeIf(o -> o.getProvider().equals(operation.getProvider()) && o.isDisabled());
}
if (!operation.isDisabled() && selected.stream().noneMatch(o -> o.getProvider().equals(operation.getProvider()))) {
selected.add(operation);
}
all.add(operation);
if (!all.contains(operation) && all.stream().noneMatch(o -> o.getProvider().equals(operation.getProvider()))) {
all.add(operation);
}
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();

View file

@ -71,6 +71,14 @@ public class ScanDialogBase {
// Previous scan operation could have exited the shell
var sc = entry.getStore().getOrStartSession();
// Multi-selection compat check
if (entries.size() > 1) {
var supported = a.getProvider().create(entry.get(), sc);
if (supported == null || supported.isDisabled()) {
continue;
}
}
try {
a.getProvider().scan(entry.get(), sc);
} catch (Throwable ex) {
@ -109,7 +117,7 @@ public class ScanDialogBase {
});
}
public Comp<?> createContent() {
public Comp<?> createComp() {
StackPane stackPane = new StackPane();
stackPane.getStyleClass().add("scan-list");
VBox.setVgrow(stackPane, ALWAYS);

View file

@ -1,7 +1,10 @@
package io.xpipe.app.util;
import io.xpipe.app.comp.base.ModalOverlayContentComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStoreEntryRef;
import javafx.beans.property.BooleanProperty;
@ -33,9 +36,11 @@ class ScanMultiDialogComp extends ModalOverlayContentComp {
}
void finish() {
ThreadHelper.runFailableAsync(() -> {
try {
base.finish();
});
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
}
BooleanProperty getBusy() {
@ -44,7 +49,7 @@ class ScanMultiDialogComp extends ModalOverlayContentComp {
@Override
protected Region createSimple() {
var list = base.createContent();
var list = base.createComp();
var b = new OptionsBuilder()
.name("scanAlertHeader")
.description("scanAlertHeaderDescription")

View file

@ -57,7 +57,7 @@ class ScanSingleDialogComp extends ModalOverlayContentComp {
@Override
protected Region createSimple() {
var list = base.createContent();
var list = base.createComp();
var b = new OptionsBuilder()
.name("scanAlertChoiceHeader")
.description("scanAlertChoiceHeaderDescription")

View file

@ -18,7 +18,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
@Value
public class ShellStoreFormat {
public class StoreStateFormat {
public static ObservableValue<String> shellEnvironment(StoreSection section, boolean includeOsName) {
return Bindings.createStringBinding(
@ -28,7 +28,7 @@ public class ShellStoreFormat {
var def = Boolean.TRUE.equals(s.getSetDefault()) ? AppI18n.get("default") : null;
var name = DataStoreFormatter.join(
(includeOsName ? formattedOsName(s.getOsName()) : null), s.getShellName());
return new ShellStoreFormat(null, name, def).format();
return new StoreStateFormat(null, name, def).format();
},
AppI18n.activeLanguage(),
section.getWrapper().getPersistentState());
@ -43,7 +43,7 @@ public class ShellStoreFormat {
if (s.getShellDialect() != null
&& !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
if (s.getOsName() != null) {
return new ShellStoreFormat(
return new StoreStateFormat(
LicenseProvider.get().checkOsName(s.getOsName()),
formattedOsName(s.getOsName()),
info)
@ -51,10 +51,10 @@ public class ShellStoreFormat {
}
if (s.getShellDialect().equals(ShellDialects.NO_INTERACTION)) {
return new ShellStoreFormat(null, null, info).format();
return new StoreStateFormat(null, null, info).format();
}
return new ShellStoreFormat(
return new StoreStateFormat(
LicenseProvider.get()
.getFeature(s.getShellDialect().getLicenseFeatureId()),
s.getShellDialect().getDisplayName(),
@ -66,7 +66,7 @@ public class ShellStoreFormat {
Stream.of(s.getTtyState() != null && s.getTtyState() != ShellTtyState.NONE ? "TTY" : null),
info != null ? Arrays.stream(info) : Stream.of())
.toArray(String[]::new);
return new ShellStoreFormat(
return new StoreStateFormat(
LicenseProvider.get().checkOsName(s.getOsName()), formattedOsName(s.getOsName()), joined)
.format();
});
@ -76,7 +76,7 @@ public class ShellStoreFormat {
String name;
String[] states;
public ShellStoreFormat(LicensedFeature licensedFeature, String name, String... states) {
public StoreStateFormat(LicensedFeature licensedFeature, String name, String... states) {
this.licensedFeature = licensedFeature;
this.name = name;
this.states = states;

View file

@ -125,6 +125,11 @@
-fx-border-color: -color-fg-subtle;
}
.root.nord .icon-button-comp.batch-mode-button {
-fx-border-radius: 0;
-fx-background-radius: 0;
}
.batch-mode-button .ikonli-font-icon {
-fx-icon-color: -color-fg-default;
}

View file

@ -10,6 +10,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.util.CommandDialog;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.process.SystemState;
import io.xpipe.ext.base.script.ScriptHierarchy;
@ -19,6 +20,7 @@ import javafx.beans.value.ObservableValue;
import lombok.Value;
import java.util.LinkedHashMap;
import java.util.List;
public class RunScriptActionMenu implements ActionProvider {
@ -87,8 +89,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2c-code-greater-than";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2c-code-greater-than");
}
@Override
@ -114,26 +116,18 @@ public class RunScriptActionMenu implements ActionProvider {
ScriptHierarchy hierarchy;
@Value
private class Action implements ActionProvider.Action {
DataStoreEntryRef<ShellStore> shellStore;
@Override
public void execute() throws Exception {
var sc = shellStore.getStore().getOrStartSession();
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
var cmd = sc.command(script);
CommandDialog.runAsyncAndShow(cmd);
}
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<ShellStore>() {
@Override
public Action createAction(DataStoreEntryRef<ShellStore> store) {
return new Action(store);
return () -> {
var sc = store.getStore().getOrStartSession();
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
var cmd = sc.command(script);
CommandDialog.runAsyncAndShow(cmd);
};
}
@Override
@ -163,8 +157,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2d-desktop-mac";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2d-desktop-mac");
}
@Override
@ -173,8 +167,17 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
return new Action(store);
public Action createAction(List<DataStoreEntryRef<ShellStore>> stores) {
return () -> {
var map = new LinkedHashMap<String, CommandControl>();
for (DataStoreEntryRef<ShellStore> ref : stores) {
var sc = ref.getStore().getOrStartSession();
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
var cmd = sc.command(script);
map.put(ref.get().getName(), cmd);
}
CommandDialog.runAsyncAndShow(map);
};
}
};
}
@ -239,8 +242,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2f-flip-to-back";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2f-flip-to-back");
}
@Override
@ -341,8 +344,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2p-play-box-multiple-outline";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2p-play-box-multiple-outline");
}
@Override
@ -416,8 +419,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2i-image-filter-none";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2i-image-filter-none");
}
@Override
@ -426,13 +429,8 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
return null;
}
@Override
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
return List.of();
public ActionProvider.Action createAction(List<DataStoreEntryRef<ShellStore>> stores) {
return new Action();
}
};
}
@ -440,14 +438,6 @@ public class RunScriptActionMenu implements ActionProvider {
private static class NoStateActionProvider implements ActionProvider {
private static class Action implements ActionProvider.Action {
@Override
public void execute() {
StoreViewState.get().getAllScriptsCategory().select();
}
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<ShellStore>() {
@ -477,6 +467,36 @@ public class RunScriptActionMenu implements ActionProvider {
}
};
}
@Override
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
return new BatchDataStoreCallSite<ShellStore>() {
@Override
public ObservableValue<String> getName() {
return AppI18n.observable("noScriptStateAvailable");
}
@Override
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2i-image-filter-none");
}
@Override
public Class<?> getApplicableClass() {
return ShellStore.class;
}
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
return new Action() {
@Override
public void execute() {
store.get().validate();
}
};
}
};
}
}
@Override
@ -569,19 +589,44 @@ public class RunScriptActionMenu implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2p-play-box-multiple-outline";
}
@Override
public Action createAction(DataStoreEntryRef<ShellStore> store) {
return null;
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2p-play-box-multiple-outline");
}
@Override
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
var hierarchy = ScriptHierarchy.buildEnabledHierarchy(ref -> {
if (!ref.getStore().isRunnableScript()) {
var stateMissing = batch.stream().anyMatch(ref -> {
var state = ref.get().getStorePersistentState();
if (state instanceof SystemState systemState) {
if (systemState.getShellDialect() == null) {
return true;
}
if (systemState.getTtyState() == null || systemState.getTtyState() != ShellTtyState.NONE) {
return true;
}
}
return false;
});
if (stateMissing) {
return List.of(new NoStateActionProvider());
}
var hierarchy = ScriptHierarchy.buildEnabledHierarchy(scriptRef -> {
var compatible = batch.stream().allMatch(ref -> {
var state = ref.get().getStorePersistentState();
if (state instanceof SystemState systemState) {
return scriptRef.getStore().getMinimumDialect().isCompatibleTo(systemState.getShellDialect());
} else {
return false;
}
});
if (!compatible) {
return false;
}
if (!scriptRef.getStore().isRunnableScript()) {
return false;
}

View file

@ -75,8 +75,8 @@ public class ScanStoreAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2l-layers-plus";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2l-layers-plus");
}
@Override

View file

@ -10,7 +10,7 @@ import io.xpipe.app.ext.SingletonSessionStoreProvider;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.ShellStoreFormat;
import io.xpipe.app.util.StoreStateFormat;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
@ -127,7 +127,7 @@ public abstract class AbstractServiceStoreProvider implements SingletonSessionSt
: s.isSessionRunning()
? AppI18n.get("active")
: s.isSessionEnabled() ? AppI18n.get("starting") : AppI18n.get("inactive");
return new ShellStoreFormat(null, desc, type, state).format();
return new StoreStateFormat(null, desc, type, state).format();
},
section.getWrapper().getCache(),
AppI18n.activeLanguage());

View file

@ -9,7 +9,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ShellStoreFormat;
import io.xpipe.app.util.StoreStateFormat;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
@ -57,7 +57,7 @@ public class ServiceControlStoreProvider implements SingletonSessionStoreProvide
var state = s.isSessionRunning()
? AppI18n.get("active")
: s.isSessionEnabled() ? AppI18n.get("starting") : AppI18n.get("inactive");
return new ShellStoreFormat(null, state).format();
return new StoreStateFormat(null, state).format();
},
section.getWrapper().getCache(),
AppPrefs.get().language());

View file

@ -63,8 +63,8 @@ public class ServiceRefreshAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2w-web";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2w-web");
}
@Override

View file

@ -11,7 +11,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.terminal.TerminalPromptManager;
import io.xpipe.app.util.ShellStoreFormat;
import io.xpipe.app.util.StoreStateFormat;
import io.xpipe.ext.base.script.ScriptStoreSetup;
import javafx.beans.property.BooleanProperty;
@ -60,6 +60,6 @@ public interface ShellStoreProvider extends DataStoreProvider {
@Override
default ObservableValue<String> informationString(StoreSection section) {
return ShellStoreFormat.shellStore(section, state -> null);
return StoreStateFormat.shellStore(section, state -> null);
}
}

View file

@ -52,8 +52,8 @@ public class StorePauseAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2p-pause";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2p-pause");
}
@Override

View file

@ -58,8 +58,8 @@ public class StoreRestartAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2r-restart";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2r-restart");
}
@Override

View file

@ -52,8 +52,8 @@ public class StoreStartAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2p-play";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2p-play");
}
@Override

View file

@ -52,8 +52,8 @@ public class StoreStopAction implements ActionProvider {
}
@Override
public String getIcon() {
return "mdi2s-stop";
public LabelGraphic getIcon() {
return new LabelGraphic.IconGraphic("mdi2s-stop");
}
@Override

View file

@ -85,7 +85,7 @@ public class IncusContainerStoreProvider implements ShellStoreProvider {
public ObservableValue<String> informationString(StoreSection section) {
var c = (ContainerStoreState) section.getWrapper().getPersistentState().getValue();
var missing = c.getShellMissing() != null && c.getShellMissing() ? "No shell available" : null;
return ShellStoreFormat.shellStore(section, (ContainerStoreState s) ->
return StoreStateFormat.shellStore(section, (ContainerStoreState s) ->
new String[] {missing, DataStoreFormatter.capitalize(s.getContainerState())});
}

View file

@ -14,7 +14,7 @@ public class IncusScanProvider extends ScanProvider {
return null;
}
return new ScanOpportunity("system.incusContainers", !new IncusCommandView(sc).isSupported(), true);
return new ScanOpportunity("system.incusContainers", !new IncusCommandView(sc).isSupported());
}
@Override

View file

@ -9,7 +9,7 @@ import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ShellStoreFormat;
import io.xpipe.app.util.StoreStateFormat;
import io.xpipe.core.store.DataStore;
import io.xpipe.ext.base.identity.IdentityChoice;
import io.xpipe.ext.base.store.ShellStoreProvider;
@ -82,7 +82,7 @@ public class LxdContainerStoreProvider implements ShellStoreProvider {
public ObservableValue<String> informationString(StoreSection section) {
var c = (ContainerStoreState) section.getWrapper().getPersistentState().getValue();
var missing = c.getShellMissing() != null && c.getShellMissing() ? "No shell available" : null;
return ShellStoreFormat.shellStore(section, (ContainerStoreState s) ->
return StoreStateFormat.shellStore(section, (ContainerStoreState s) ->
new String[] {missing, DataStoreFormatter.capitalize(s.getContainerState())});
}

View file

@ -15,7 +15,7 @@ public class LxdScanProvider extends ScanProvider {
return null;
}
return new ScanOpportunity("system.lxdContainers", !new LxdCommandView(sc).isSupported(), true);
return new ScanOpportunity("system.lxdContainers", !new LxdCommandView(sc).isSupported());
}
@Override

View file

@ -10,7 +10,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ShellStoreFormat;
import io.xpipe.app.util.StoreStateFormat;
import io.xpipe.app.util.SimpleValidator;
import io.xpipe.core.store.DataStore;
import io.xpipe.ext.base.service.FixedServiceGroupStore;
@ -83,7 +83,7 @@ public class PodmanContainerStoreProvider implements ShellStoreProvider {
public ObservableValue<String> informationString(StoreSection section) {
var c = (ContainerStoreState) section.getWrapper().getPersistentState().getValue();
var missing = c.getShellMissing() != null && c.getShellMissing() ? "No shell available" : null;
return ShellStoreFormat.shellStore(
return StoreStateFormat.shellStore(
section, (ContainerStoreState s) -> new String[] {missing, s.getContainerState()});
}

View file

@ -10,7 +10,7 @@ public class PodmanScanProvider extends ScanProvider {
@Override
public ScanOpportunity create(DataStoreEntry entry, ShellControl sc) throws Exception {
var view = new PodmanCommandView(sc);
return new ScanOpportunity("system.podmanContainers", !view.isSupported(), true);
return new ScanOpportunity("system.podmanContainers", !view.isSupported());
}
@Override

View file

@ -1 +1 @@
16.0-21
16.0-22