mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
SSH config fixes
This commit is contained in:
parent
bc9b962be9
commit
880b17c7c1
61 changed files with 650 additions and 788 deletions
|
@ -108,7 +108,6 @@ run {
|
|||
}
|
||||
|
||||
workingDir = rootDir
|
||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
||||
}
|
||||
|
||||
task runAttachedDebugger(type: JavaExec) {
|
||||
|
@ -122,7 +121,7 @@ task runAttachedDebugger(type: JavaExec) {
|
|||
"-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.4.jar=port:7857,host:localhost".toString(),
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
|
||||
)
|
||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
||||
jvmArgs += '-XX:+EnableDynamicAgentLoading'
|
||||
systemProperties run.systemProperties
|
||||
}
|
||||
|
||||
|
|
|
@ -6,16 +6,18 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -68,8 +70,9 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
||||
|
||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
|
||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
|
||||
var recent = ListBindingsHelper.mappedContentBinding(
|
||||
model.getSavedState().getRecentDirectories(),
|
||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
|
||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
||||
import io.xpipe.app.fxcomps.impl.*;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -47,7 +47,7 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
var backgroundStack =
|
||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
||||
|
||||
var binding = new DerivedObservableList<>(syncItems, true).mapped(item -> item.getBrowserEntry()).getList();
|
||||
var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry());
|
||||
var list = new BrowserSelectionListComp(
|
||||
binding,
|
||||
entry -> Bindings.createStringBinding(
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
|
@ -15,9 +13,10 @@ import io.xpipe.app.fxcomps.impl.LabelComp;
|
|||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -31,6 +30,9 @@ import javafx.scene.layout.Priority;
|
|||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BrowserWelcomeComp extends SimpleComp {
|
||||
|
@ -65,7 +67,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
return new VBox(hbox);
|
||||
}
|
||||
|
||||
var list = new DerivedObservableList<>(state.getEntries(), true).filtered(e -> {
|
||||
var list = ListBindingsHelper.filteredContentBinding(state.getEntries(), e -> {
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
if (entry.isEmpty()) {
|
||||
return false;
|
||||
|
@ -76,7 +78,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
}
|
||||
|
||||
return true;
|
||||
}).getList();
|
||||
});
|
||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||
|
||||
var headerBinding = BindingsHelper.flatMap(empty, b -> {
|
||||
|
|
|
@ -24,10 +24,6 @@ public class BrowserEntry {
|
|||
}
|
||||
|
||||
private static BrowserIconFileType fileType(FileSystem.FileEntry rawFileEntry) {
|
||||
if (rawFileEntry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rawFileEntry.getKind() == FileKind.DIRECTORY) {
|
||||
return null;
|
||||
}
|
||||
|
@ -42,10 +38,6 @@ public class BrowserEntry {
|
|||
}
|
||||
|
||||
private static BrowserIconDirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
|
||||
if (rawFileEntry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rawFileEntry.getKind() != FileKind.DIRECTORY) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser.session;
|
|||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
|
@ -10,10 +10,12 @@ import io.xpipe.app.util.ThreadHelper;
|
|||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
@ -38,8 +40,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
|||
return;
|
||||
}
|
||||
|
||||
var l = new DerivedObservableList<>(fileSelection, true);
|
||||
l.bindContent(newValue.getFileList().getSelection());
|
||||
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -39,13 +38,10 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
|
|||
}))
|
||||
.createRegion();
|
||||
|
||||
List<? extends ObservableValue<Boolean>> l = cm.getItems().stream()
|
||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||
.toList();
|
||||
button.visibleProperty()
|
||||
.bind(Bindings.createBooleanBinding(() -> {
|
||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
||||
}, l.toArray(ObservableValue[]::new)));
|
||||
.bind(ListBindingsHelper.anyMatch(cm.getItems().stream()
|
||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||
.toList()));
|
||||
|
||||
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
||||
button.fontProperty().subscribe(c -> {
|
||||
|
|
|
@ -3,8 +3,9 @@ package io.xpipe.app.comp.base;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
@ -88,8 +89,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
}
|
||||
|
||||
if (!listView.getChildren().equals(newShown)) {
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
ListBindingsHelper.setContent(listView.getChildren(), newShown);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ public class StoreToggleComp extends SimpleComp {
|
|||
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
|
||||
v -> {
|
||||
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
|
||||
section.getWrapper().refreshChildren();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public class StoreCategoryWrapper {
|
|||
}
|
||||
|
||||
public StoreCategoryWrapper getParent() {
|
||||
return StoreViewState.get().getCategories().getList().stream()
|
||||
return StoreViewState.get().getCategories().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
||||
.findAny()
|
||||
|
@ -122,7 +122,7 @@ public class StoreCategoryWrapper {
|
|||
sortMode.setValue(category.getSortMode());
|
||||
share.setValue(category.isShare());
|
||||
|
||||
containedEntries.setAll(StoreViewState.get().getAllEntries().getList().stream()
|
||||
containedEntries.setAll(StoreViewState.get().getAllEntries().stream()
|
||||
.filter(entry -> {
|
||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
||||
|| (AppPrefs.get()
|
||||
|
@ -132,7 +132,7 @@ public class StoreCategoryWrapper {
|
|||
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
||||
})
|
||||
.toList());
|
||||
children.setAll(StoreViewState.get().getCategories().getList().stream()
|
||||
children.setAll(StoreViewState.get().getCategories().stream()
|
||||
.filter(storeCategoryWrapper -> getCategory()
|
||||
.getUuid()
|
||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||
|
|
|
@ -286,6 +286,10 @@ public class StoreCreationComp extends DialogComp {
|
|||
if (ex instanceof ValidationException) {
|
||||
ErrorEvent.expected(ex);
|
||||
skippable.set(false);
|
||||
} else if (ex instanceof StackOverflowError) {
|
||||
// Cycles in connection graphs can fail hard but are expected
|
||||
ErrorEvent.expected(ex);
|
||||
skippable.set(false);
|
||||
} else {
|
||||
skippable.set(true);
|
||||
}
|
||||
|
|
|
@ -372,14 +372,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
||||
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2n-note-text"));
|
||||
notes.setOnAction(event -> {
|
||||
wrapper.getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
|
||||
event.consume();
|
||||
});
|
||||
notes.visibleProperty().bind(BindingsHelper.map(wrapper.getNotes(), s -> s.getCommited() == null));
|
||||
contextMenu.getItems().add(notes);
|
||||
|
||||
if (AppPrefs.get().developerMode().getValue()) {
|
||||
var browse = new MenuItem(AppI18n.get("browseInternalStorage"), new FontIcon("mdi2f-folder-open-outline"));
|
||||
browse.setOnAction(
|
||||
|
@ -387,6 +379,26 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
contextMenu.getItems().add(browse);
|
||||
}
|
||||
|
||||
if (wrapper.getEntry().getProvider() != null) {
|
||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||
StoreViewState.get()
|
||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||
.forEach(storeCategoryWrapper -> {
|
||||
MenuItem m = new MenuItem();
|
||||
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
|
||||
m.setOnAction(event -> {
|
||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||
event.consume();
|
||||
});
|
||||
if (storeCategoryWrapper.getParent() == null || storeCategoryWrapper.equals(wrapper.getCategory().getValue())) {
|
||||
m.setDisable(true);
|
||||
}
|
||||
|
||||
move.getItems().add(m);
|
||||
});
|
||||
contextMenu.getItems().add(move);
|
||||
}
|
||||
|
||||
if (DataStorage.get().isRootEntry(wrapper.getEntry())) {
|
||||
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
||||
var none = new MenuItem("None");
|
||||
|
@ -406,72 +418,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
contextMenu.getItems().add(color);
|
||||
}
|
||||
|
||||
if (wrapper.getEntry().getProvider() != null) {
|
||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||
StoreViewState.get()
|
||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||
.getList()
|
||||
.forEach(storeCategoryWrapper -> {
|
||||
MenuItem m = new MenuItem();
|
||||
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
|
||||
m.setOnAction(event -> {
|
||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||
event.consume();
|
||||
});
|
||||
if (storeCategoryWrapper.getParent() == null) {
|
||||
m.setDisable(true);
|
||||
}
|
||||
|
||||
move.getItems().add(m);
|
||||
});
|
||||
contextMenu.getItems().add(move);
|
||||
}
|
||||
|
||||
var order = new Menu(AppI18n.get("order"), new FontIcon("mdal-bookmarks"));
|
||||
var noOrder = new MenuItem(AppI18n.get("none"), new FontIcon("mdi2r-reorder-horizontal"));
|
||||
noOrder.setOnAction(event -> {
|
||||
DataStorage.get().orderBefore(wrapper.getEntry(), null);
|
||||
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2n-note-text"));
|
||||
notes.setOnAction(event -> {
|
||||
wrapper.getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
|
||||
event.consume();
|
||||
});
|
||||
if (wrapper.getEntry().getOrderBefore() == null) {
|
||||
noOrder.setDisable(true);
|
||||
}
|
||||
order.getItems().add(noOrder);
|
||||
order.getItems().add(new SeparatorMenuItem());
|
||||
var stick = new MenuItem(AppI18n.get("stickToTop"), new FontIcon("mdi2o-order-bool-descending"));
|
||||
stick.setOnAction(event -> {
|
||||
DataStorage.get().orderBefore(wrapper.getEntry(), wrapper.getEntry());
|
||||
event.consume();
|
||||
});
|
||||
if (wrapper.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
|
||||
stick.setDisable(true);
|
||||
}
|
||||
order.getItems().add(stick);
|
||||
order.getItems().add(new SeparatorMenuItem());
|
||||
var desc = new MenuItem(AppI18n.get("orderAheadOf"), new FontIcon("mdi2o-order-bool-descending-variant"));
|
||||
desc.setDisable(true);
|
||||
order.getItems().add(desc);
|
||||
var section = StoreViewState.get().getParentSectionForWrapper(wrapper);
|
||||
if (section.isPresent()) {
|
||||
section.get().getAllChildren().getList().forEach(other -> {
|
||||
var ow = other.getWrapper();
|
||||
var op = ow.getEntry().getProvider();
|
||||
MenuItem m = new MenuItem(ow.getName().getValue(),
|
||||
op != null ? PrettyImageHelper.ofFixedSizeSquare(op.getDisplayIconFileName(ow.getEntry().getStore()),
|
||||
16).createRegion() : null);
|
||||
if (other.getWrapper().equals(wrapper) || ow.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
|
||||
m.setDisable(true);
|
||||
}
|
||||
m.setOnAction(event -> {
|
||||
wrapper.orderBefore(ow);
|
||||
event.consume();
|
||||
});
|
||||
order.getItems().add(m);
|
||||
});
|
||||
}
|
||||
contextMenu.getItems().add(order);
|
||||
|
||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||
notes.visibleProperty().bind(BindingsHelper.map(wrapper.getNotes(), s -> s.getCommited() == null));
|
||||
contextMenu.getItems().add(notes);
|
||||
|
||||
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||
del.disableProperty()
|
||||
|
|
|
@ -18,8 +18,8 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
|
||||
private Comp<?> createList() {
|
||||
var content = new ListBoxViewComp<>(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
|
||||
(StoreSection e) -> {
|
||||
var custom = StoreSection.customSection(e, true).hgrow();
|
||||
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
|
||||
|
@ -35,7 +35,7 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
var showIntro = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
var all = StoreViewState.get().getAllConnectionsCategory();
|
||||
var connections = StoreViewState.get().getAllEntries().getList().stream()
|
||||
var connections = StoreViewState.get().getAllEntries().stream()
|
||||
.filter(wrapper -> all.contains(wrapper))
|
||||
.toList();
|
||||
return initialCount == connections.size()
|
||||
|
@ -45,21 +45,21 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
.getRoot()
|
||||
.equals(StoreViewState.get().getAllConnectionsCategory());
|
||||
},
|
||||
StoreViewState.get().getAllEntries().getList(),
|
||||
StoreViewState.get().getAllEntries(),
|
||||
StoreViewState.get().getActiveCategory());
|
||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(
|
||||
createList(),
|
||||
Bindings.not(Bindings.isEmpty(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||
|
||||
map.put(new StoreIntroComp(), showIntro);
|
||||
map.put(
|
||||
new StoreNotFoundComp(),
|
||||
Bindings.and(
|
||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries().getList())),
|
||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
|
||||
Bindings.isEmpty(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||
return new MultiContentComp(map).createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
|||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -55,24 +56,25 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
label.textProperty().bind(name);
|
||||
label.getStyleClass().add("name");
|
||||
|
||||
var all = StoreViewState.get().getAllEntries().filtered(
|
||||
var all = ListBindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
storeEntryWrapper -> {
|
||||
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
|
||||
// Sadly the all binding does not update when the individual visibility of entries changes
|
||||
// But it is good enough.
|
||||
var showProvider = storeEntryWrapper.getEntry().getProvider() == null ||
|
||||
storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
|
||||
return inRootCategory && showProvider;
|
||||
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||
return StoreViewState.get()
|
||||
.getActiveCategory()
|
||||
.getValue()
|
||||
.getRoot()
|
||||
.equals(storeRoot);
|
||||
},
|
||||
StoreViewState.get().getActiveCategory());
|
||||
var shownList = all.filtered(
|
||||
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||
all,
|
||||
storeEntryWrapper -> {
|
||||
return storeEntryWrapper.shouldShow(
|
||||
StoreViewState.get().getFilterString().getValue());
|
||||
},
|
||||
StoreViewState.get().getFilterString());
|
||||
var count = new CountComp<>(shownList.getList(), all.getList());
|
||||
var count = new CountComp<>(shownList, all);
|
||||
|
||||
var c = count.createRegion();
|
||||
var topBar = new HBox(
|
||||
|
|
|
@ -9,13 +9,17 @@ import io.xpipe.app.storage.DataStoreCategory;
|
|||
import io.xpipe.app.storage.DataStoreColor;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.Observable;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class StoreEntryWrapper {
|
||||
|
@ -61,22 +65,12 @@ public class StoreEntryWrapper {
|
|||
setupListeners();
|
||||
}
|
||||
|
||||
public List<Observable> getUpdateObservables() {
|
||||
return List.of(category);
|
||||
}
|
||||
|
||||
public void moveTo(DataStoreCategory category) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().updateCategory(entry, category);
|
||||
});
|
||||
}
|
||||
|
||||
public void orderBefore(StoreEntryWrapper other) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().orderBefore(getEntry(),other.getEntry());
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isInStorage() {
|
||||
return DataStorage.get().getStoreEntries().contains(entry);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||
}
|
||||
|
||||
private ContextMenu createMenu() {
|
||||
if (section.getShownChildren().getList().isEmpty()) {
|
||||
if (section.getShownChildren().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||
var w = section.getWrapper();
|
||||
var graphic =
|
||||
w.getEntry().getProvider().getDisplayIconFileName(w.getEntry().getStore());
|
||||
if (c.getList().isEmpty()) {
|
||||
if (c.isEmpty()) {
|
||||
var item = ContextMenuHelper.item(
|
||||
PrettyImageHelper.ofFixedSizeSquare(graphic, 16),
|
||||
w.getName().getValue());
|
||||
|
@ -55,7 +55,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||
}
|
||||
|
||||
var items = new ArrayList<MenuItem>();
|
||||
for (StoreSection sub : c.getList()) {
|
||||
for (StoreSection sub : c) {
|
||||
if (!sub.getWrapper().getValidity().getValue().isUsable()) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -2,37 +2,37 @@ package io.xpipe.app.comp.store;
|
|||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableStringValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
@Value
|
||||
public class StoreSection {
|
||||
|
||||
StoreEntryWrapper wrapper;
|
||||
DerivedObservableList<StoreSection> allChildren;
|
||||
DerivedObservableList<StoreSection> shownChildren;
|
||||
ObservableList<StoreSection> allChildren;
|
||||
ObservableList<StoreSection> shownChildren;
|
||||
int depth;
|
||||
ObservableBooleanValue showDetails;
|
||||
|
||||
public StoreSection(
|
||||
StoreEntryWrapper wrapper,
|
||||
DerivedObservableList<StoreSection> allChildren,
|
||||
DerivedObservableList<StoreSection> shownChildren,
|
||||
ObservableList<StoreSection> allChildren,
|
||||
ObservableList<StoreSection> shownChildren,
|
||||
int depth) {
|
||||
this.wrapper = wrapper;
|
||||
this.allChildren = allChildren;
|
||||
|
@ -41,10 +41,10 @@ public class StoreSection {
|
|||
if (wrapper != null) {
|
||||
this.showDetails = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
|
||||
return wrapper.getExpanded().get() || allChildren.isEmpty();
|
||||
},
|
||||
wrapper.getExpanded(),
|
||||
allChildren.getList());
|
||||
allChildren);
|
||||
} else {
|
||||
this.showDetails = new SimpleBooleanProperty(true);
|
||||
}
|
||||
|
@ -59,77 +59,51 @@ public class StoreSection {
|
|||
}
|
||||
}
|
||||
|
||||
private static DerivedObservableList<StoreSection> sorted(
|
||||
DerivedObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
private static ObservableList<StoreSection> sorted(
|
||||
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
if (category == null) {
|
||||
return list;
|
||||
}
|
||||
|
||||
var explicitOrderComp = Comparator.<StoreSection>comparingInt(new ToIntFunction<>() {
|
||||
@Override
|
||||
public int applyAsInt(StoreSection value) {
|
||||
var explicit = value.getWrapper().getEntry().getOrderBefore();
|
||||
if (explicit == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (explicit.equals(value.getWrapper().getEntry().getUuid())) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
return -count(value.getWrapper(), new HashSet<>());
|
||||
}
|
||||
|
||||
private int count(StoreEntryWrapper wrapper, Set<StoreEntryWrapper> seen) {
|
||||
if (seen.contains(wrapper)) {
|
||||
// Loop!
|
||||
return 0;
|
||||
}
|
||||
seen.add(wrapper);
|
||||
|
||||
var found = list.getList().stream().filter(section -> wrapper.getEntry().getOrderBefore().equals(section.getWrapper().getEntry().getUuid())).findFirst();
|
||||
if (found.isPresent()) {
|
||||
return count(found.get().getWrapper(), seen);
|
||||
} else {
|
||||
return seen.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
var usableComp = Comparator.<StoreSection>comparingInt(
|
||||
var c = Comparator.<StoreSection>comparingInt(
|
||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
||||
var comp = explicitOrderComp.thenComparing(usableComp);
|
||||
var mappedSortMode = BindingsHelper.flatMap(
|
||||
category,
|
||||
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||
return list.sorted((o1, o2) -> {
|
||||
return ListBindingsHelper.orderedContentBinding(
|
||||
list,
|
||||
(o1, o2) -> {
|
||||
var current = mappedSortMode.getValue();
|
||||
if (current != null) {
|
||||
return comp.thenComparing(current.comparator())
|
||||
return c.thenComparing(current.comparator())
|
||||
.compare(current.representative(o1), current.representative(o2));
|
||||
} else {
|
||||
return comp.compare(o1, o2);
|
||||
return c.compare(o1, o2);
|
||||
}
|
||||
},
|
||||
mappedSortMode,
|
||||
StoreViewState.get().getEntriesOrderChangeObservable());
|
||||
mappedSortMode);
|
||||
}
|
||||
|
||||
public static StoreSection createTopLevel(
|
||||
DerivedObservableList<StoreEntryWrapper> all,
|
||||
ObservableList<StoreEntryWrapper> all,
|
||||
Predicate<StoreEntryWrapper> entryFilter,
|
||||
ObservableStringValue filterString,
|
||||
ObservableValue<StoreCategoryWrapper> category) {
|
||||
var topLevel = all.filtered(section -> {
|
||||
var topLevel = ListBindingsHelper.filteredContentBinding(
|
||||
all,
|
||||
section -> {
|
||||
return DataStorage.get().isRootEntry(section.getEntry());
|
||||
},
|
||||
category,
|
||||
StoreViewState.get().getEntriesListChangeObservable());
|
||||
var cached = topLevel.mapped(
|
||||
category);
|
||||
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||
topLevel,
|
||||
topLevel,
|
||||
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
||||
var ordered = sorted(cached, category);
|
||||
var shown = ordered.filtered(
|
||||
var shown = ListBindingsHelper.filteredContentBinding(
|
||||
ordered,
|
||||
section -> {
|
||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
var sameCategory = category == null
|
||||
|| category.getValue() == null
|
||||
|
@ -144,17 +118,15 @@ public class StoreSection {
|
|||
private static StoreSection create(
|
||||
StoreEntryWrapper e,
|
||||
int depth,
|
||||
DerivedObservableList<StoreEntryWrapper> all,
|
||||
ObservableList<StoreEntryWrapper> all,
|
||||
Predicate<StoreEntryWrapper> entryFilter,
|
||||
ObservableStringValue filterString,
|
||||
ObservableValue<StoreCategoryWrapper> category) {
|
||||
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return new StoreSection(e, new DerivedObservableList<>(
|
||||
FXCollections.observableArrayList(), true), new DerivedObservableList<>(
|
||||
FXCollections.observableArrayList(), true), depth);
|
||||
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
|
||||
}
|
||||
|
||||
var allChildren = all.filtered(other -> {
|
||||
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> {
|
||||
// Legacy implementation that does not use children caches. Use for testing
|
||||
// if (true) return DataStorage.get()
|
||||
// .getDisplayParent(other.getEntry())
|
||||
|
@ -162,35 +134,29 @@ public class StoreSection {
|
|||
// .orElse(false);
|
||||
|
||||
// This check is fast as the children are cached in the storage
|
||||
var isChildren = DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||
var showProvider = other.getEntry().getProvider() == null ||
|
||||
other.getEntry().getProvider().shouldShow(other);
|
||||
return isChildren && showProvider;
|
||||
}, e.getPersistentState(), e.getCache(), StoreViewState.get().getEntriesListChangeObservable());
|
||||
var cached = allChildren.mapped(
|
||||
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||
});
|
||||
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||
allChildren,
|
||||
allChildren,
|
||||
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
||||
var ordered = sorted(cached, category);
|
||||
var filtered = ordered.filtered(
|
||||
var filtered = ListBindingsHelper.filteredContentBinding(
|
||||
ordered,
|
||||
section -> {
|
||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
// Prevent updates for children on category switching by checking depth
|
||||
var showCategory = category == null
|
||||
var sameCategory = category == null
|
||||
|| category.getValue() == null
|
||||
|| showInCategory(category.getValue(), section.getWrapper())
|
||||
|| depth > 0;
|
||||
|| showInCategory(category.getValue(), section.getWrapper());
|
||||
// If this entry is already shown as root due to a different category than parent, don't show it
|
||||
// again here
|
||||
var notRoot =
|
||||
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
||||
var showProvider = section.getWrapper().getEntry().getProvider() == null ||
|
||||
section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper());
|
||||
return showFilter && matchesSelector && showCategory && notRoot && showProvider;
|
||||
return showFilter && matchesSelector && sameCategory && notRoot;
|
||||
},
|
||||
category,
|
||||
filterString,
|
||||
e.getPersistentState(),
|
||||
e.getCache());
|
||||
filterString);
|
||||
return new StoreSection(e, cached, filtered, depth);
|
||||
}
|
||||
|
||||
|
@ -213,13 +179,13 @@ public class StoreSection {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean matchesFilter(String filter) {
|
||||
public boolean shouldShow(String filter) {
|
||||
return anyMatches(storeEntryWrapper -> storeEntryWrapper.shouldShow(filter));
|
||||
}
|
||||
|
||||
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
|
||||
return c == null
|
||||
|| c.test(wrapper)
|
||||
|| allChildren.getList().stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
|
||||
|| allChildren.stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
|
|||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStoreColor;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -40,9 +42,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
return section.getShownChildren().isEmpty();
|
||||
},
|
||||
section.getShownChildren().getList());
|
||||
section.getShownChildren());
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
w.executeDefaultAction();
|
||||
|
@ -69,11 +71,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
var expandButton = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().getList().size() > 0
|
||||
&& section.getShownChildren().size() > 0
|
||||
? "mdal-keyboard_arrow_down"
|
||||
: "mdal-keyboard_arrow_right",
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren().getList()),
|
||||
section.getShownChildren()),
|
||||
() -> {
|
||||
section.getWrapper().toggleExpanded();
|
||||
});
|
||||
|
@ -87,7 +89,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||
.styleClass("expand-button")
|
||||
.maxHeight(100)
|
||||
.vgrow();
|
||||
|
@ -126,12 +128,13 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = section.getShownChildren().filtered(
|
||||
storeSection -> section.getAllChildren().getList().size() <= 20
|
||||
var listSections = ListBindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20
|
||||
|| section.getWrapper().getExpanded().get(),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getAllChildren().getList());
|
||||
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
|
||||
section.getAllChildren());
|
||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||
})
|
||||
.minHeight(0)
|
||||
|
@ -140,10 +143,10 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
var expanded = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().getList().size() > 0;
|
||||
&& section.getShownChildren().size() > 0;
|
||||
},
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren().getList());
|
||||
section.getShownChildren());
|
||||
var full = new VerticalComp(List.of(
|
||||
topEntryList,
|
||||
Comp.separator().hide(expanded.not()),
|
||||
|
@ -152,7 +155,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(section.getWrapper().getExpanded()),
|
||||
Bindings.size(section.getShownChildren().getList()).isEqualTo(0)))));
|
||||
Bindings.size(section.getShownChildren()).isEqualTo(0)))));
|
||||
return full.styleClass("store-entry-section-comp")
|
||||
.apply(struc -> {
|
||||
struc.get().setFillWidth(true);
|
||||
|
|
|
@ -8,7 +8,9 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
|||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStoreColor;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -82,7 +84,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
expanded =
|
||||
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().getList().size() > 0);
|
||||
&& section.getShownChildren().size() > 0);
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right",
|
||||
|
@ -99,15 +101,15 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
+ section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||
.grow(false, true)
|
||||
.styleClass("expand-button");
|
||||
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
return section.getShownChildren().isEmpty();
|
||||
},
|
||||
section.getShownChildren().getList());
|
||||
section.getShownChildren());
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = action;
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||
.vgrow()
|
||||
|
@ -129,12 +131,13 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = section.getWrapper() != null
|
||||
? section.getShownChildren().filtered(
|
||||
storeSection -> section.getAllChildren().getList().size() <= 20 || expanded.get(),
|
||||
? ListBindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||
expanded,
|
||||
section.getAllChildren().getList())
|
||||
section.getAllChildren())
|
||||
: section.getShownChildren();
|
||||
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
|
||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
|
||||
})
|
||||
.minHeight(0)
|
||||
|
@ -145,7 +148,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(expanded),
|
||||
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
|
||||
Bindings.size(section.getAllChildren()).isEqualTo(0))));
|
||||
|
||||
var vert = new VerticalComp(list);
|
||||
if (condensedStyle) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public interface StoreSortMode {
|
|||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().getList().stream()
|
||||
s.getShownChildren().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
|
@ -76,7 +76,7 @@ public interface StoreSortMode {
|
|||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().getList().stream()
|
||||
s.getShownChildren().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.StorageListener;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
@ -23,18 +29,12 @@ public class StoreViewState {
|
|||
private final StringProperty filter = new SimpleStringProperty();
|
||||
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> allEntries =
|
||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
||||
private final ObservableList<StoreEntryWrapper> allEntries =
|
||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreCategoryWrapper> categories =
|
||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
||||
|
||||
@Getter
|
||||
private final IntegerProperty entriesOrderChangeObservable = new SimpleIntegerProperty();
|
||||
|
||||
@Getter
|
||||
private final IntegerProperty entriesListChangeObservable = new SimpleIntegerProperty();
|
||||
private final ObservableList<StoreCategoryWrapper> categories =
|
||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||
|
||||
@Getter
|
||||
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
||||
|
@ -76,8 +76,8 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
private void updateContent() {
|
||||
categories.getList().forEach(c -> c.update());
|
||||
allEntries.getList().forEach(e -> e.update());
|
||||
categories.forEach(c -> c.update());
|
||||
allEntries.forEach(e -> e.update());
|
||||
}
|
||||
|
||||
private void initSections() {
|
||||
|
@ -86,19 +86,16 @@ public class StoreViewState {
|
|||
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
|
||||
} catch (Exception exception) {
|
||||
currentTopLevelSection =
|
||||
new StoreSection(null,
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
0);
|
||||
new StoreSection(null, FXCollections.emptyObservableList(), FXCollections.emptyObservableList(), 0);
|
||||
ErrorEvent.fromThrowable(exception).handle();
|
||||
}
|
||||
}
|
||||
|
||||
private void initContent() {
|
||||
allEntries.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||
allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||
.map(StoreEntryWrapper::new)
|
||||
.toList()));
|
||||
categories.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
||||
categories.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
||||
.map(StoreCategoryWrapper::new)
|
||||
.toList()));
|
||||
|
||||
|
@ -106,11 +103,11 @@ public class StoreViewState {
|
|||
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
||||
});
|
||||
var selected = AppCache.get("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
||||
activeCategory.setValue(categories.getList().stream()
|
||||
activeCategory.setValue(categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
||||
.findFirst()
|
||||
.orElse(categories.getList().stream()
|
||||
.orElse(categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.DEFAULT_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
|
@ -122,9 +119,9 @@ public class StoreViewState {
|
|||
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
synchronized (this) {
|
||||
var l = new ArrayList<>(allEntries.getList());
|
||||
allEntries.getList().clear();
|
||||
allEntries.getList().setAll(l);
|
||||
var l = new ArrayList<>(allEntries);
|
||||
allEntries.clear();
|
||||
allEntries.setAll(l);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -132,21 +129,6 @@ public class StoreViewState {
|
|||
|
||||
// Watch out for synchronizing all calls to the entries and categories list!
|
||||
DataStorage.get().addListener(new StorageListener() {
|
||||
|
||||
@Override
|
||||
public void onStoreOrderUpdate() {
|
||||
Platform.runLater(() -> {
|
||||
entriesOrderChangeObservable.set(entriesOrderChangeObservable.get() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreListUpdate() {
|
||||
Platform.runLater(() -> {
|
||||
entriesListChangeObservable.set(entriesListChangeObservable.get() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreAdd(DataStoreEntry... entry) {
|
||||
var l = Arrays.stream(entry)
|
||||
|
@ -160,11 +142,11 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
allEntries.getList().addAll(l);
|
||||
allEntries.addAll(l);
|
||||
}
|
||||
synchronized (this) {
|
||||
categories.getList().stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
||||
categories.stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||
.getEntry()
|
||||
.getCategoryUuid()
|
||||
|
@ -181,14 +163,14 @@ public class StoreViewState {
|
|||
var a = Arrays.stream(entry).collect(Collectors.toSet());
|
||||
List<StoreEntryWrapper> l;
|
||||
synchronized (this) {
|
||||
l = allEntries.getList().stream()
|
||||
l = allEntries.stream()
|
||||
.filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry()))
|
||||
.toList();
|
||||
}
|
||||
List<StoreCategoryWrapper> cats;
|
||||
synchronized (this) {
|
||||
cats = categories.getList().stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
||||
cats = categories.stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||
.getEntry()
|
||||
.getCategoryUuid()
|
||||
|
@ -204,7 +186,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
allEntries.getList().removeAll(l);
|
||||
allEntries.removeAll(l);
|
||||
}
|
||||
cats.forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
||||
});
|
||||
|
@ -221,7 +203,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
categories.getList().add(l);
|
||||
categories.add(l);
|
||||
}
|
||||
l.update();
|
||||
});
|
||||
|
@ -231,7 +213,7 @@ public class StoreViewState {
|
|||
public void onCategoryRemove(DataStoreCategory category) {
|
||||
Optional<StoreCategoryWrapper> found;
|
||||
synchronized (this) {
|
||||
found = categories.getList().stream()
|
||||
found = categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().equals(category))
|
||||
.findFirst();
|
||||
|
@ -247,7 +229,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
categories.getList().remove(found.get());
|
||||
categories.remove(found.get());
|
||||
}
|
||||
var p = found.get().getParent();
|
||||
if (p != null) {
|
||||
|
@ -258,34 +240,15 @@ public class StoreViewState {
|
|||
});
|
||||
}
|
||||
|
||||
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
|
||||
StoreSection current = getCurrentTopLevelSection();
|
||||
while (true) {
|
||||
var child = current.getAllChildren().getList().stream().filter(section -> section.getWrapper().equals(wrapper)).findFirst();
|
||||
if (child.isPresent()) {
|
||||
return Optional.of(current);
|
||||
}
|
||||
|
||||
var traverse = current.getAllChildren().getList().stream().filter(section -> section.anyMatches(w -> w.equals(wrapper))).findFirst();
|
||||
if (traverse.isPresent()) {
|
||||
current = traverse.get();
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DerivedObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
|
||||
public ObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
|
||||
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
|
||||
@Override
|
||||
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
|
||||
var o1Root = o1.getRoot();
|
||||
var o2Root = o2.getRoot();
|
||||
|
||||
if (o1Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (o2Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
||||
return 1;
|
||||
}
|
||||
|
@ -302,6 +265,22 @@ public class StoreViewState {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (o1.getDepth() > o2.getDepth()) {
|
||||
if (o1.getParent() == o2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return compare(o1.getParent(), o2);
|
||||
}
|
||||
|
||||
if (o1.getDepth() < o2.getDepth()) {
|
||||
if (o2.getParent() == o1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compare(o1, o2.getParent());
|
||||
}
|
||||
|
||||
var parent = compare(o1.getParent(), o2.getParent());
|
||||
if (parent != 0) {
|
||||
return parent;
|
||||
|
@ -312,11 +291,13 @@ public class StoreViewState {
|
|||
.compareToIgnoreCase(o2.nameProperty().getValue());
|
||||
}
|
||||
};
|
||||
return categories.filtered(cat -> root == null || cat.getRoot().equals(root)).sorted(comparator);
|
||||
return ListBindingsHelper.filteredContentBinding(
|
||||
categories, cat -> root == null || cat.getRoot().equals(root))
|
||||
.sorted(comparator);
|
||||
}
|
||||
|
||||
public StoreCategoryWrapper getAllConnectionsCategory() {
|
||||
return categories.getList().stream()
|
||||
return categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
|
@ -324,7 +305,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public StoreCategoryWrapper getAllScriptsCategory() {
|
||||
return categories.getList().stream()
|
||||
return categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
|
@ -332,14 +313,14 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
||||
return allEntries.getList().stream()
|
||||
return allEntries.stream()
|
||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
|
||||
return categories.getList().stream()
|
||||
return categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().equals(entry))
|
||||
.findFirst()
|
||||
|
|
|
@ -28,10 +28,6 @@ import java.util.List;
|
|||
|
||||
public interface DataStoreProvider {
|
||||
|
||||
default boolean shouldShow(StoreEntryWrapper w) {
|
||||
return true;
|
||||
}
|
||||
|
||||
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
|
||||
return new SimpleBooleanProperty(false);
|
||||
}
|
||||
|
|
|
@ -4,14 +4,17 @@ import io.xpipe.app.core.AppI18n;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.Translatable;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
|
@ -76,7 +79,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
list.add(null);
|
||||
}
|
||||
|
||||
cb.getItems().setAll(list);
|
||||
ListBindingsHelper.setContent(cb.getItems(), list);
|
||||
});
|
||||
|
||||
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
|
|
|
@ -11,10 +11,11 @@ import io.xpipe.app.core.AppI18n;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
|
@ -25,6 +26,7 @@ import javafx.scene.control.MenuItem;
|
|||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
@ -77,12 +79,13 @@ public class StoreCategoryComp extends SimpleComp {
|
|||
showing.bind(cm.showingProperty());
|
||||
return cm;
|
||||
}));
|
||||
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
|
||||
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||
category.getContainedEntries(),
|
||||
storeEntryWrapper -> {
|
||||
return storeEntryWrapper.shouldShow(
|
||||
StoreViewState.get().getFilterString().getValue());
|
||||
},
|
||||
StoreViewState.get().getFilterString()).getList();
|
||||
StoreViewState.get().getFilterString());
|
||||
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
|
||||
var hover = new SimpleBooleanProperty();
|
||||
var focus = new SimpleBooleanProperty();
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
package io.xpipe.app.fxcomps.util;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Getter
|
||||
public class DerivedObservableList<T> {
|
||||
|
||||
private final ObservableList<T> list;
|
||||
private final boolean unique;
|
||||
|
||||
public DerivedObservableList(ObservableList<T> list, boolean unique) {
|
||||
this.list = list;
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
private <V> DerivedObservableList<V> createNewDerived() {
|
||||
var l = FXCollections.<V>observableArrayList();
|
||||
BindingsHelper.preserve(l, list);
|
||||
return new DerivedObservableList<>(l, unique);
|
||||
}
|
||||
|
||||
public void setContent(List<? extends T> newList) {
|
||||
if (list.equals(newList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (list.size() == 0) {
|
||||
list.addAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newList.size() == 0) {
|
||||
list.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (unique) {
|
||||
setContentUnique(newList);
|
||||
} else {
|
||||
setContentNonUnique(newList);
|
||||
}
|
||||
}
|
||||
|
||||
public void setContentNonUnique(List<? extends T> newList) {
|
||||
var target = list;
|
||||
var targetSet = new HashSet<>(target);
|
||||
var newSet = new HashSet<>(newList);
|
||||
|
||||
// Only add missing element
|
||||
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
|
||||
var l = new HashSet<>(newSet);
|
||||
l.removeAll(targetSet);
|
||||
if (l.size() > 0) {
|
||||
var found = l.iterator().next();
|
||||
var index = newList.indexOf(found);
|
||||
target.add(index, found);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Only remove not needed element
|
||||
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
|
||||
var l = new HashSet<>(targetSet);
|
||||
l.removeAll(newSet);
|
||||
if (l.size() > 0) {
|
||||
target.remove(l.iterator().next());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Other cases are more difficult
|
||||
target.setAll(newList);
|
||||
}
|
||||
|
||||
private void setContentUnique(List<? extends T> newList) {
|
||||
var listSet = new HashSet<>(list);
|
||||
var newSet = new HashSet<>(newList);
|
||||
// Addition
|
||||
if (newSet.containsAll(list)) {
|
||||
var l = new ArrayList<>(newList);
|
||||
l.removeIf(t -> !listSet.contains(t));
|
||||
// Reordering occurred
|
||||
if (!l.equals(list)) {
|
||||
list.setAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
var start = 0;
|
||||
for (int end = 0; end <= list.size(); end++) {
|
||||
var index = end < list.size() ? newList.indexOf(list.get(end)) : newList.size();
|
||||
for (; start < index; start++) {
|
||||
list.add(start, newList.get(start));
|
||||
}
|
||||
start = index + 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Removal
|
||||
if (listSet.containsAll(newList)) {
|
||||
var l = new ArrayList<>(list);
|
||||
l.removeIf(t -> !newSet.contains(t));
|
||||
// Reordering occurred
|
||||
if (!l.equals(newList)) {
|
||||
list.setAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
var toRemove = new ArrayList<>(list);
|
||||
toRemove.removeIf(t -> newSet.contains(t));
|
||||
list.removeAll(toRemove);
|
||||
return;
|
||||
}
|
||||
|
||||
// Other cases are more difficult
|
||||
list.setAll(newList);
|
||||
}
|
||||
|
||||
public <V> DerivedObservableList<V> mapped(Function<T, V> map) {
|
||||
var l1 = this.<V>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
l1.setContent(list.stream().map(map).toList());
|
||||
};
|
||||
runnable.run();
|
||||
list.addListener((ListChangeListener<? super T>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
return l1;
|
||||
}
|
||||
|
||||
public void bindContent(ObservableList<T> other) {
|
||||
setContent(other);
|
||||
other.addListener((ListChangeListener<? super T>) c -> {
|
||||
setContent(other);
|
||||
});
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> filtered(Predicate<T> predicate) {
|
||||
return filtered(new SimpleObjectProperty<>(predicate));
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> filtered(Predicate<T> predicate, Observable... observables) {
|
||||
return filtered(
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(T v) {
|
||||
return predicate.test(v);
|
||||
}
|
||||
};
|
||||
},
|
||||
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> filtered(ObservableValue<Predicate<T>> predicate) {
|
||||
var d = this.<T>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
d.setContent(
|
||||
predicate.getValue() != null
|
||||
? list.stream().filter(predicate.getValue()).toList()
|
||||
: list);
|
||||
};
|
||||
runnable.run();
|
||||
list.addListener((ListChangeListener<? super T>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
predicate.addListener(observable -> {
|
||||
runnable.run();
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> sorted(Comparator<T> comp, Observable... observables) {
|
||||
return sorted(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return new Comparator<>() {
|
||||
@Override
|
||||
public int compare(T o1, T o2) {
|
||||
return comp.compare(o1, o2);
|
||||
}
|
||||
};
|
||||
},
|
||||
observables));
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> sorted(ObservableValue<Comparator<T>> comp) {
|
||||
var d = this.<T>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
d.setContent(list.stream().sorted(comp.getValue()).toList());
|
||||
};
|
||||
runnable.run();
|
||||
list.addListener((ListChangeListener<? super T>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
comp.addListener(observable -> {
|
||||
d.list.sort(comp.getValue());
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
public DerivedObservableList<T> blockUpdatesIf(ObservableBooleanValue block) {
|
||||
var d = this.<T>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
d.setContent(list);
|
||||
};
|
||||
runnable.run();
|
||||
list.addListener((ListChangeListener<? super T>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
block.addListener(observable -> {
|
||||
runnable.run();
|
||||
});
|
||||
return d;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
package io.xpipe.app.fxcomps.util;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class ListBindingsHelper {
|
||||
|
||||
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
|
||||
setContent(l1, l2);
|
||||
l2.addListener((ListChangeListener<? super T>) c -> {
|
||||
setContent(l1, l2);
|
||||
});
|
||||
}
|
||||
|
||||
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
|
||||
return Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
||||
},
|
||||
l.toArray(ObservableValue[]::new));
|
||||
}
|
||||
|
||||
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
|
||||
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
||||
Runnable runnable = () -> {
|
||||
setContent(l1, l2.stream().map(map).toList());
|
||||
};
|
||||
runnable.run();
|
||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
BindingsHelper.preserve(l1, l2);
|
||||
return l1;
|
||||
}
|
||||
|
||||
public static <T, V> ObservableList<T> cachedMappedContentBinding(
|
||||
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
|
||||
var cache = new HashMap<V, T>();
|
||||
|
||||
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
||||
Runnable runnable = () -> {
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
setContent(
|
||||
l1,
|
||||
shown.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, map.apply(v));
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.toList());
|
||||
};
|
||||
runnable.run();
|
||||
shown.addListener((ListChangeListener<? super V>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
BindingsHelper.preserve(l1, all);
|
||||
BindingsHelper.preserve(l1, shown);
|
||||
return l1;
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> orderedContentBinding(
|
||||
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
|
||||
return orderedContentBinding(
|
||||
l2,
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return new Comparator<>() {
|
||||
@Override
|
||||
public int compare(V o1, V o2) {
|
||||
return comp.compare(o1, o2);
|
||||
}
|
||||
};
|
||||
},
|
||||
observables));
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> orderedContentBinding(
|
||||
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
|
||||
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
||||
Runnable runnable = () -> {
|
||||
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
|
||||
};
|
||||
runnable.run();
|
||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
comp.addListener((observable, oldValue, newValue) -> {
|
||||
runnable.run();
|
||||
});
|
||||
BindingsHelper.preserve(l1, l2);
|
||||
return l1;
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
|
||||
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> filteredContentBinding(
|
||||
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
|
||||
return filteredContentBinding(
|
||||
l2,
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(V v) {
|
||||
return predicate.test(v);
|
||||
}
|
||||
};
|
||||
},
|
||||
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> filteredContentBinding(
|
||||
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
|
||||
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
||||
Runnable runnable = () -> {
|
||||
setContent(
|
||||
l1,
|
||||
predicate.getValue() != null
|
||||
? l2.stream().filter(predicate.getValue()).toList()
|
||||
: l2);
|
||||
};
|
||||
runnable.run();
|
||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||
runnable.run();
|
||||
});
|
||||
predicate.addListener((c, o, n) -> {
|
||||
runnable.run();
|
||||
});
|
||||
BindingsHelper.preserve(l1, l2);
|
||||
return l1;
|
||||
}
|
||||
|
||||
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
|
||||
if (target.equals(newList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.size() == 0) {
|
||||
target.setAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newList.size() == 0) {
|
||||
target.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var targetSet = new HashSet<>(target);
|
||||
var newSet = new HashSet<>(newList);
|
||||
|
||||
// Only add missing element
|
||||
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
|
||||
var l = new HashSet<>(newSet);
|
||||
l.removeAll(targetSet);
|
||||
if (l.size() > 0) {
|
||||
var found = l.iterator().next();
|
||||
var index = newList.indexOf(found);
|
||||
target.add(index, found);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Only remove not needed element
|
||||
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
|
||||
var l = new HashSet<>(targetSet);
|
||||
l.removeAll(newSet);
|
||||
if (l.size() > 0) {
|
||||
target.remove(l.iterator().next());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Other cases are more difficult
|
||||
target.setAll(newList);
|
||||
}
|
||||
}
|
|
@ -45,16 +45,17 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
@Override
|
||||
public boolean isAvailable() {
|
||||
try (ShellControl pc = LocalShell.getShell().start()) {
|
||||
return pc.command(String.format(
|
||||
var out = pc.command(String.format(
|
||||
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications",
|
||||
applicationName))
|
||||
.executeAndCheck();
|
||||
.readStdoutIfPossible();
|
||||
return out.isPresent() && !out.get().isBlank();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSelectable() {
|
||||
return OsType.getLocal().equals(OsType.MACOS);
|
||||
|
|
|
@ -194,10 +194,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
public void launch(Path file) throws Exception {
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted(applicationName)
|
||||
.addFile(file.toString()));
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
sc.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a").addQuoted(applicationName).addFile(file.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -328,16 +328,16 @@ public abstract class DataStorage {
|
|||
return;
|
||||
}
|
||||
|
||||
entry.setCategoryUuid(newCategory.getUuid());
|
||||
var children = getDeepStoreChildren(entry);
|
||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||
listeners.forEach(storageListener -> storageListener.onStoreListUpdate());
|
||||
saveAsync();
|
||||
}
|
||||
var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||
|
||||
public void orderBefore(DataStoreEntry entry, DataStoreEntry reference) {
|
||||
entry.setOrderBefore(reference != null ? reference.getUuid() : null);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreOrderUpdate());
|
||||
entry.setCategoryUuid(newCategory.getUuid());
|
||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||
|
||||
var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public boolean refreshChildren(DataStoreEntry e) {
|
||||
|
@ -439,8 +439,8 @@ public abstract class DataStorage {
|
|||
pair.getKey().setStoreInternal(merged, false);
|
||||
}
|
||||
|
||||
var s = pair.getKey().getStorePersistentState();
|
||||
var mergedState = s.mergeCopy(pair.getValue().get().getStorePersistentState());
|
||||
var mergedState = pair.getKey().getStorePersistentState().deepCopy();
|
||||
mergedState.merge(pair.getValue().get().getStorePersistentState());
|
||||
pair.getKey().setStorePersistentState(mergedState);
|
||||
}
|
||||
}
|
||||
|
@ -788,7 +788,9 @@ public abstract class DataStorage {
|
|||
|
||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
|
||||
return storeEntriesSet.stream()
|
||||
.filter(n -> n.getStore() == store || (!identityOnly && (n.getStore() != null
|
||||
.filter(n -> n.getStore() == store
|
||||
|| (!identityOnly
|
||||
&& (n.getStore() != null
|
||||
&& Objects.equals(
|
||||
store.getClass(), n.getStore().getClass())
|
||||
&& store.equals(n.getStore()))))
|
||||
|
|
|
@ -72,9 +72,6 @@ public class DataStoreEntry extends StorageElement {
|
|||
@NonFinal
|
||||
String notes;
|
||||
|
||||
@NonFinal
|
||||
UUID orderBefore;
|
||||
|
||||
private DataStoreEntry(
|
||||
Path directory,
|
||||
UUID uuid,
|
||||
|
@ -89,8 +86,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
JsonNode storePersistentState,
|
||||
boolean expanded,
|
||||
DataStoreColor color,
|
||||
String notes, UUID orderBefore
|
||||
) {
|
||||
String notes) {
|
||||
super(directory, uuid, name, lastUsed, lastModified, dirty);
|
||||
this.categoryUuid = categoryUuid;
|
||||
this.store = DataStorageParser.storeFromNode(storeNode);
|
||||
|
@ -99,7 +95,6 @@ public class DataStoreEntry extends StorageElement {
|
|||
this.configuration = configuration;
|
||||
this.expanded = expanded;
|
||||
this.color = color;
|
||||
this.orderBefore = orderBefore;
|
||||
this.provider = store != null
|
||||
? DataStoreProviders.byStoreClass(store.getClass()).orElse(null)
|
||||
: null;
|
||||
|
@ -114,12 +109,10 @@ public class DataStoreEntry extends StorageElement {
|
|||
String name,
|
||||
Instant lastUsed,
|
||||
Instant lastModified,
|
||||
DataStore store, UUID orderBefore
|
||||
) {
|
||||
DataStore store) {
|
||||
super(directory, uuid, name, lastUsed, lastModified, false);
|
||||
this.categoryUuid = categoryUuid;
|
||||
this.store = store;
|
||||
this.orderBefore = orderBefore;
|
||||
this.storeNode = null;
|
||||
this.validity = Validity.INCOMPLETE;
|
||||
this.configuration = Configuration.defaultConfiguration();
|
||||
|
@ -137,8 +130,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
UUID.randomUUID().toString(),
|
||||
Instant.now(),
|
||||
Instant.now(),
|
||||
store,
|
||||
null);
|
||||
store);
|
||||
}
|
||||
|
||||
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
|
||||
|
@ -167,7 +159,6 @@ public class DataStoreEntry extends StorageElement {
|
|||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
return entry;
|
||||
}
|
||||
|
@ -185,8 +176,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
JsonNode storePersistentState,
|
||||
boolean expanded,
|
||||
DataStoreColor color,
|
||||
String notes,
|
||||
UUID orderBeforeEntry) {
|
||||
String notes) {
|
||||
return new DataStoreEntry(
|
||||
directory,
|
||||
uuid,
|
||||
|
@ -201,8 +191,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
storePersistentState,
|
||||
expanded,
|
||||
color,
|
||||
notes,
|
||||
orderBeforeEntry);
|
||||
notes);
|
||||
}
|
||||
|
||||
public static Optional<DataStoreEntry> fromDirectory(Path dir) throws Exception {
|
||||
|
@ -237,15 +226,6 @@ public class DataStoreEntry extends StorageElement {
|
|||
.map(jsonNode -> jsonNode.textValue())
|
||||
.map(Instant::parse)
|
||||
.orElse(Instant.EPOCH);
|
||||
var order = Optional.ofNullable(stateJson.get("orderBefore"))
|
||||
.map(node -> {
|
||||
try {
|
||||
return mapper.treeToValue(node, UUID.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.orElse(null);
|
||||
var configuration = Optional.ofNullable(json.get("configuration"))
|
||||
.map(node -> {
|
||||
try {
|
||||
|
@ -295,19 +275,10 @@ public class DataStoreEntry extends StorageElement {
|
|||
persistentState,
|
||||
expanded,
|
||||
color,
|
||||
notes,
|
||||
order
|
||||
notes
|
||||
));
|
||||
}
|
||||
|
||||
public void setOrderBefore(UUID uuid) {
|
||||
var changed = !Objects.equals(orderBefore, uuid);
|
||||
this.orderBefore = uuid;
|
||||
if (changed) {
|
||||
notifyUpdate(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getUuid().hashCode();
|
||||
|
@ -359,7 +330,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
storePersistentStateNode = JacksonMapper.getDefault().valueToTree(storePersistentState);
|
||||
}
|
||||
}
|
||||
return (T) storePersistentState;
|
||||
return (T) sds.getStateClass().cast(storePersistentState);
|
||||
}
|
||||
|
||||
public void setStorePersistentState(DataStoreState value) {
|
||||
|
@ -408,7 +379,6 @@ public class DataStoreEntry extends StorageElement {
|
|||
stateObj.set("persistentState", storePersistentStateNode);
|
||||
obj.set("configuration", mapper.valueToTree(configuration));
|
||||
stateObj.put("expanded", expanded);
|
||||
stateObj.put("orderBefore", orderBefore != null ? orderBefore.toString() : null);
|
||||
|
||||
var entryString = mapper.writeValueAsString(obj);
|
||||
var stateString = mapper.writeValueAsString(stateObj);
|
||||
|
|
|
@ -2,10 +2,6 @@ package io.xpipe.app.storage;
|
|||
|
||||
public interface StorageListener {
|
||||
|
||||
void onStoreOrderUpdate();
|
||||
|
||||
void onStoreListUpdate();
|
||||
|
||||
void onStoreAdd(DataStoreEntry... entry);
|
||||
|
||||
void onStoreRemove(DataStoreEntry... entry);
|
||||
|
|
|
@ -514,17 +514,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
var suffix = "\"" + configuration.getScriptFile().toString().replaceAll("\"", "\\\\\"") + "\"";
|
||||
pc.osascriptCommand(String.format(
|
||||
"""
|
||||
activate application "Terminal"
|
||||
delay 1
|
||||
tell app "Terminal" to do script %s
|
||||
""",
|
||||
suffix))
|
||||
.execute();
|
||||
}
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Terminal.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
||||
|
@ -550,26 +544,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
pc.osascriptCommand(String.format(
|
||||
"""
|
||||
if application "iTerm" is not running then
|
||||
launch application "iTerm"
|
||||
delay 1
|
||||
tell application "iTerm"
|
||||
tell current tab of current window
|
||||
close
|
||||
end tell
|
||||
end tell
|
||||
end if
|
||||
tell application "iTerm"
|
||||
activate
|
||||
create window with default profile command "%s"
|
||||
end tell
|
||||
""",
|
||||
configuration.getScriptFile().toString().replaceAll("\"", "\\\\\"")))
|
||||
.execute();
|
||||
}
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("iTerm.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
@ -26,7 +31,7 @@ public interface WezTerminalType extends ExternalTerminalType {
|
|||
|
||||
@Override
|
||||
default boolean isRecommended() {
|
||||
return false;
|
||||
return OsType.getLocal() != OsType.WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,25 +56,62 @@ public interface WezTerminalType extends ExternalTerminalType {
|
|||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
Optional<String> launcherDir;
|
||||
launcherDir = WindowsRegistry.local().readValue(
|
||||
WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1",
|
||||
"InstallLocation")
|
||||
.map(p -> p + "\\wezterm-gui.exe");
|
||||
return launcherDir.map(Path::of);
|
||||
try {
|
||||
var foundKey = WindowsRegistry.local().findKeyForEqualValueMatchRecursive(WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "http://wezfurlong.org/wezterm");
|
||||
if (foundKey.isPresent()) {
|
||||
var installKey = WindowsRegistry.local().readValue(
|
||||
foundKey.get().getHkey(),
|
||||
foundKey.get().getKey(),
|
||||
"InstallLocation");
|
||||
if (installKey.isPresent()) {
|
||||
return installKey.map(p -> p + "\\wezterm-gui.exe").map(Path::of);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
}
|
||||
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
if (pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand("wezterm-gui"))) {
|
||||
return Optional.of(Path.of("wezterm-gui"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class Linux extends SimplePathType implements WezTerminalType {
|
||||
class Linux extends ExternalApplicationType implements WezTerminalType {
|
||||
|
||||
public Linux() {
|
||||
super("app.wezterm", "wezterm-gui", true);
|
||||
super("app.wezterm");
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand("wezterm")) &&
|
||||
pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand("wezterm-gui"));
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
||||
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var spawn = LocalShell.getShell().command(CommandBuilder.of().addFile("wezterm")
|
||||
.add("cli", "spawn")
|
||||
.addFile(configuration.getScriptFile()))
|
||||
.executeAndCheck();
|
||||
if (!spawn) {
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.addFile("wezterm-gui")
|
||||
.add("start")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,20 +123,27 @@ public interface WezTerminalType extends ExternalTerminalType {
|
|||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var path = LocalShell.getShell()
|
||||
.command(String.format(
|
||||
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
||||
applicationName))
|
||||
.readStdoutOrThrow();
|
||||
var c = CommandBuilder.of()
|
||||
.addFile(Path.of(path)
|
||||
.resolve("Contents")
|
||||
.resolve("MacOS")
|
||||
.resolve("wezterm-gui")
|
||||
.toString())
|
||||
.add("start")
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
ExternalApplicationHelper.startAsync(c);
|
||||
try (var sc = LocalShell.getShell()) {
|
||||
var path = sc.command(
|
||||
String.format("mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
||||
applicationName)).readStdoutOrThrow();
|
||||
var spawn = sc.command(CommandBuilder.of().addFile(Path.of(path)
|
||||
.resolve("Contents")
|
||||
.resolve("MacOS")
|
||||
.resolve("wezterm").toString())
|
||||
.add("cli", "spawn", "--pane-id", "0")
|
||||
.addFile(configuration.getScriptFile()))
|
||||
.executeAndCheck();
|
||||
if (!spawn) {
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.addFile(Path.of(path)
|
||||
.resolve("Contents")
|
||||
.resolve("MacOS")
|
||||
.resolve("wezterm-gui").toString())
|
||||
.add("start")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
|
|||
value.setValue(newValue);
|
||||
}
|
||||
});
|
||||
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root).getList());
|
||||
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root));
|
||||
box.setValue(value.getValue());
|
||||
box.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
value.setValue(newValue);
|
||||
|
|
|
@ -28,10 +28,9 @@ public class FileOpener {
|
|||
try {
|
||||
editor.launch(Path.of(localFile).toRealPath());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e)
|
||||
.description("Unable to launch editor "
|
||||
ErrorEvent.fromThrowable("Unable to launch editor "
|
||||
+ editor.toTranslatedString().getValue()
|
||||
+ ".\nMaybe try to use a different editor in the settings.")
|
||||
+ ".\nMaybe try to use a different editor in the settings.", e)
|
||||
.expected()
|
||||
.handle();
|
||||
}
|
||||
|
@ -52,8 +51,7 @@ public class FileOpener {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e)
|
||||
.description("Unable to open file " + localFile)
|
||||
ErrorEvent.fromThrowable("Unable to open file " + localFile, e)
|
||||
.handle();
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +66,7 @@ public class FileOpener {
|
|||
pc.executeSimpleCommand("open \"" + localFile + "\"");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e)
|
||||
.description("Unable to open file " + localFile)
|
||||
ErrorEvent.fromThrowable("Unable to open file " + localFile, e)
|
||||
.handle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,6 @@ project.ext {
|
|||
arch = getArchName()
|
||||
privateExtensions = file("$rootDir/private_extensions.txt").exists() ? file("$rootDir/private_extensions.txt").readLines() : []
|
||||
isFullRelease = System.getenv('RELEASE') != null && Boolean.parseBoolean(System.getenv('RELEASE'))
|
||||
isPreRelease = System.getenv('PRERELEASE') != null && Boolean.parseBoolean(System.getenv('PRERELEASE'))
|
||||
isStage = System.getenv('STAGE') != null && Boolean.parseBoolean(System.getenv('STAGE'))
|
||||
rawVersion = file('version').text.trim()
|
||||
versionString = rawVersion + (isFullRelease || isStage ? '' : '-SNAPSHOT')
|
||||
|
@ -106,7 +105,7 @@ project.ext {
|
|||
website = 'https://xpipe.io'
|
||||
sourceWebsite = isStage ? 'https://github.com/xpipe-io/xpipe-ptb' : 'https://github.com/xpipe-io/xpipe'
|
||||
authors = 'Christopher Schnick'
|
||||
javafxVersion = '22.0.1'
|
||||
javafxVersion = '23-ea+18'
|
||||
platformName = getPlatformName()
|
||||
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
||||
jvmRunArgs = [
|
||||
|
@ -159,6 +158,11 @@ if (isFullRelease && rawVersion.contains("-")) {
|
|||
throw new IllegalArgumentException("Releases must have canonical versions")
|
||||
}
|
||||
|
||||
|
||||
if (isStage && !rawVersion.contains("-")) {
|
||||
throw new IllegalArgumentException("Stage releases must have release numbers")
|
||||
}
|
||||
|
||||
def replaceVariablesInFileAsString(String f, Map<String, String> replacements) {
|
||||
def fileName = file(f).getName()
|
||||
def text = file(f).text
|
||||
|
|
|
@ -58,13 +58,12 @@ public interface ShellControl extends ProcessControl {
|
|||
|
||||
default <T extends ShellStoreState> ShellControl withShellStateInit(StatefulDataStore<T> store) {
|
||||
return onInit(shellControl -> {
|
||||
var s = store.getState().toBuilder()
|
||||
.osType(shellControl.getOsType())
|
||||
.shellDialect(shellControl.getOriginalShellDialect())
|
||||
.running(true)
|
||||
.osName(shellControl.getOsName())
|
||||
.build();
|
||||
store.setState(s.asNeeded());
|
||||
var s = store.getState();
|
||||
s.setOsType(shellControl.getOsType());
|
||||
s.setShellDialect(shellControl.getOriginalShellDialect());
|
||||
s.setRunning(true);
|
||||
s.setOsName(shellControl.getOsName());
|
||||
store.setState(s);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -75,8 +74,9 @@ public interface ShellControl extends ProcessControl {
|
|||
return;
|
||||
}
|
||||
|
||||
var s = store.getState().toBuilder().running(false).build();
|
||||
store.setState(s.asNeeded());
|
||||
var s = store.getState();
|
||||
s.setRunning(false);
|
||||
store.setState(s);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import io.xpipe.core.store.DataStoreState;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Jacksonized
|
||||
public class ShellNameStoreState extends ShellStoreState {
|
||||
|
||||
String shellName;
|
||||
|
||||
@Override
|
||||
public DataStoreState mergeCopy(DataStoreState newer) {
|
||||
var n = (ShellNameStoreState) newer;
|
||||
var b = toBuilder();
|
||||
mergeBuilder(n,b);
|
||||
return b.shellName(useNewer(shellName, n.shellName)).build();
|
||||
}
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import io.xpipe.core.store.DataStoreState;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
@Setter
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Jacksonized
|
||||
@SuperBuilder
|
||||
public class ShellStoreState extends DataStoreState implements OsNameState {
|
||||
|
||||
OsType.Any osType;
|
||||
|
@ -25,17 +26,11 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DataStoreState mergeCopy(DataStoreState newer) {
|
||||
public void merge(DataStoreState newer) {
|
||||
var shellStoreState = (ShellStoreState) newer;
|
||||
var b = toBuilder();
|
||||
mergeBuilder(shellStoreState, b);
|
||||
return b.build();
|
||||
}
|
||||
|
||||
protected void mergeBuilder(ShellStoreState shellStoreState, ShellStoreStateBuilder<?,?> b) {
|
||||
b.osType(useNewer(osType, shellStoreState.getOsType()))
|
||||
.osName(useNewer(osName, shellStoreState.getOsName()))
|
||||
.shellDialect(useNewer(shellDialect, shellStoreState.getShellDialect()))
|
||||
.running(useNewer(running, shellStoreState.getRunning()));
|
||||
osType = useNewer(osType, shellStoreState.getOsType());
|
||||
osName = useNewer(osName, shellStoreState.getOsName());
|
||||
shellDialect = useNewer(shellDialect, shellStoreState.getShellDialect());
|
||||
running = useNewer(running, shellStoreState.getRunning());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,49 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@SuperBuilder
|
||||
public abstract class DataStoreState {
|
||||
|
||||
public DataStoreState() {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <DS extends DataStoreState> DS asNeeded() {
|
||||
return (DS) this;
|
||||
}
|
||||
|
||||
protected static <T> T useNewer(T older, T newer) {
|
||||
return newer != null ? newer : older;
|
||||
}
|
||||
|
||||
public DataStoreState mergeCopy(DataStoreState newer) {
|
||||
return this;
|
||||
public abstract void merge(DataStoreState newer);
|
||||
|
||||
@SneakyThrows
|
||||
public DataStoreState deepCopy() {
|
||||
return JacksonMapper.getDefault().treeToValue(JacksonMapper.getDefault().valueToTree(this), getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
var tree = JacksonMapper.getDefault().valueToTree(this);
|
||||
return tree.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o != null && getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var tree = JacksonMapper.getDefault().valueToTree(this);
|
||||
var otherTree = JacksonMapper.getDefault().valueToTree(o);
|
||||
return tree.equals(otherTree);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String toString() {
|
||||
var tree = JacksonMapper.getDefault().valueToTree(this);
|
||||
return tree.toPrettyString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import io.xpipe.core.util.DataStateProvider;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
|
||||
|
||||
|
@ -17,14 +19,20 @@ public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
|
|||
return getStateClass().cast(m.invoke(b));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getState() {
|
||||
return DataStateProvider.get().getState(this, this::createDefaultState);
|
||||
return (T)
|
||||
DataStateProvider.get().getState(this, this::createDefaultState).deepCopy();
|
||||
}
|
||||
|
||||
default void setState(T val) {
|
||||
DataStateProvider.get().setState(this, val);
|
||||
}
|
||||
|
||||
default T getState(Supplier<T> def) {
|
||||
return DataStateProvider.get().getState(this, def);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
default Class<T> getStateClass() {
|
||||
|
|
20
dist/changelogs/9.4_incremental.md
vendored
20
dist/changelogs/9.4_incremental.md
vendored
|
@ -8,6 +8,8 @@ The file transfer mechanism when editing files had some flaws, which under rare
|
|||
|
||||
The entire transfer implementation has been rewritten to iron out these issues and increase reliability. Other file browser actions have also been made more reliable.
|
||||
|
||||
There seems to be another separate issue with a PowerShell bug when connecting to a Windows system, causing file uploads to be slow. For now, xpipe can fall back to pwsh if it is installed to work around this issue.
|
||||
|
||||
## Git vault improvements
|
||||
|
||||
The conflict resolution has been improved
|
||||
|
@ -15,11 +17,27 @@ The conflict resolution has been improved
|
|||
- In case of a merge conflict, overwriting local changes will now preserve all connections that are not added to the git vault, including local connections
|
||||
- You now have the option to force push changes when a conflict occurs while XPipe is saving while running, not requiring a restart anymore
|
||||
|
||||
## Terminal improvements
|
||||
|
||||
The terminal integration got reworked for some terminals:
|
||||
- iTerm can now launch tabs instead of individual windows. There were also a few issues fixed that prevented it from launching sometimes
|
||||
- WezTerm now supports tabs on Linux and macOS. The Windows installation detection has been improved to detect all installed versions
|
||||
- Terminal.app will now launch faster
|
||||
|
||||
## Other
|
||||
|
||||
- You can now add simple RDP connections without a file
|
||||
- Fix VMware Player/Workstation and MSYS2 not being detected on Windows. Now simply searching for connections should add them automatically if they are installed
|
||||
- The file browser sidebar now only contains connections that can be opened in it, reducing the amount of connection shown
|
||||
- Clarify error message for RealVNC servers, highlighting that RealVNC uses a proprietary protocol spec that can't be supported by third-party VNC clients like xpipe
|
||||
- Fix Linux builds containing unnecessary debug symbols
|
||||
- Fix AUR package also installing a debug package
|
||||
- Fix application restart not working properly on macOS
|
||||
- Fix possibility of selecting own children connections as hosts, causing a stack overflow. Please don't try to create cycles in your connection graphs
|
||||
- Fix vault secrets not correctly updating unless restarted when changing vault passphrase
|
||||
- Fix connection launcher desktop shortcuts and URLs not properly executing if xpipe is not running
|
||||
- Fix move to ... menu sometimes not ordering categories correctly
|
||||
- Fix SSH command failing on macOS with homebrew openssh package installed
|
||||
- Fix SSH connections not opening the correct shell environment on Windows when username contained spaces due to an OpenSSH bug
|
||||
- Fix SSH connections not opening the correct shell environment on Windows systems when username contained spaces due to an OpenSSH bug
|
||||
- Fix newly added connections not having the correct order
|
||||
- Fix error messages of external editor programs not being shown when they failed to start
|
||||
|
|
2
dist/jpackage.gradle
vendored
2
dist/jpackage.gradle
vendored
|
@ -58,7 +58,7 @@ jlink {
|
|||
]
|
||||
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isLinux()) {
|
||||
options += ['--strip-native-debug-symbols']
|
||||
options.addAll('--strip-native-debug-symbols', 'exclude-debuginfo-files')
|
||||
}
|
||||
|
||||
if (useBundledJavaFx) {
|
||||
|
|
|
@ -31,13 +31,15 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
|
|||
|
||||
var def = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
||||
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
||||
var state = s.getState().toBuilder().isDefault(aBoolean).build();
|
||||
var state = s.getState();
|
||||
state.setDefault(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
var bring = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
||||
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
||||
var state = s.getState().toBuilder().bringToShell(aBoolean).build();
|
||||
var state = s.getState();
|
||||
state.setBringToShell(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,14 +6,15 @@ import io.xpipe.app.storage.DataStoreEntry;
|
|||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
import io.xpipe.app.util.Validators;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellInitCommand;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.DataStoreState;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.StatefulDataStore;
|
||||
import io.xpipe.core.util.JacksonizedValue;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
|
@ -221,12 +222,20 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
|||
|
||||
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
@Setter
|
||||
@Getter
|
||||
@SuperBuilder
|
||||
@Jacksonized
|
||||
public static class State extends DataStoreState {
|
||||
boolean isDefault;
|
||||
boolean bringToShell;
|
||||
|
||||
@Override
|
||||
public void merge(DataStoreState newer) {
|
||||
var s = (State) newer;
|
||||
isDefault = s.isDefault;
|
||||
bringToShell = s.bringToShell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,13 +56,15 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
|
||||
var def = StoreToggleComp.<SimpleScriptStore>simpleToggle(
|
||||
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
||||
var state = s.getState().toBuilder().isDefault(aBoolean).build();
|
||||
var state = s.getState();
|
||||
state.setDefault(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
var bring = StoreToggleComp.<SimpleScriptStore>simpleToggle(
|
||||
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
||||
var state = s.getState().toBuilder().bringToShell(aBoolean).build();
|
||||
var state = s.getState();
|
||||
state.setBringToShell(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
|
|
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-rc-1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -457,6 +457,3 @@ history=Browsing-historik
|
|||
skipAll=Spring alle over
|
||||
notes=Bemærkninger
|
||||
addNotes=Tilføj noter
|
||||
order=Bestille ...
|
||||
stickToTop=Hold dig på toppen
|
||||
orderAheadOf=Bestil på forhånd ...
|
||||
|
|
|
@ -451,6 +451,3 @@ history=Browsing-Verlauf
|
|||
skipAll=Alles überspringen
|
||||
notes=Anmerkungen
|
||||
addNotes=Notizen hinzufügen
|
||||
order=Bestellen ...
|
||||
stickToTop=Oben bleiben
|
||||
orderAheadOf=Vorbestellen ...
|
||||
|
|
|
@ -454,7 +454,3 @@ history=Browsing history
|
|||
skipAll=Skip all
|
||||
notes=Notes
|
||||
addNotes=Add notes
|
||||
#context: verb
|
||||
order=Order ...
|
||||
stickToTop=Keep on top
|
||||
orderAheadOf=Order ahead of ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=Historial de navegación
|
|||
skipAll=Saltar todo
|
||||
notes=Notas
|
||||
addNotes=Añadir notas
|
||||
order=Ordenar ...
|
||||
stickToTop=Mantener arriba
|
||||
orderAheadOf=Haz tu pedido antes de ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=Historique de navigation
|
|||
skipAll=Sauter tout
|
||||
notes=Notes
|
||||
addNotes=Ajouter des notes
|
||||
order=Commander...
|
||||
stickToTop=Garde le dessus
|
||||
orderAheadOf=Commande en avance...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=Cronologia di navigazione
|
|||
skipAll=Salta tutto
|
||||
notes=Note
|
||||
addNotes=Aggiungi note
|
||||
order=Ordinare ...
|
||||
stickToTop=Continua a essere in cima
|
||||
orderAheadOf=Ordina prima di ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=閲覧履歴
|
|||
skipAll=すべてスキップする
|
||||
notes=備考
|
||||
addNotes=メモを追加する
|
||||
order=注文する
|
||||
stickToTop=トップをキープする
|
||||
orderAheadOf=先に注文する
|
||||
|
|
|
@ -438,6 +438,3 @@ history=Browsegeschiedenis
|
|||
skipAll=Alles overslaan
|
||||
notes=Opmerkingen
|
||||
addNotes=Opmerkingen toevoegen
|
||||
order=Bestellen ...
|
||||
stickToTop=Bovenaan houden
|
||||
orderAheadOf=Vooruitbestellen ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=Histórico de navegação
|
|||
skipAll=Salta tudo
|
||||
notes=Nota
|
||||
addNotes=Adiciona notas
|
||||
order=Encomenda ...
|
||||
stickToTop=Mantém-te no topo
|
||||
orderAheadOf=Encomenda antes de ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=История просмотров
|
|||
skipAll=Пропустить все
|
||||
notes=Заметки
|
||||
addNotes=Добавляй заметки
|
||||
order=Заказать ...
|
||||
stickToTop=Держись на высоте
|
||||
orderAheadOf=Заказать заранее ...
|
||||
|
|
|
@ -439,6 +439,3 @@ history=Tarama geçmişi
|
|||
skipAll=Tümünü atla
|
||||
notes=Notlar
|
||||
addNotes=Notlar ekleyin
|
||||
order=Sipariş ...
|
||||
stickToTop=Zirvede kal
|
||||
orderAheadOf=Önceden sipariş verin ...
|
||||
|
|
|
@ -438,6 +438,3 @@ history=浏览历史
|
|||
skipAll=全部跳过
|
||||
notes=说明
|
||||
addNotes=添加注释
|
||||
order=订购 ...
|
||||
stickToTop=保持在顶部
|
||||
orderAheadOf=提前订购...
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
9.4-3
|
||||
9.4
|
||||
|
|
Loading…
Reference in a new issue