mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +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
|
workingDir = rootDir
|
||||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task runAttachedDebugger(type: JavaExec) {
|
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(),
|
"-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"
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
|
||||||
)
|
)
|
||||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
jvmArgs += '-XX:+EnableDynamicAgentLoading'
|
||||||
systemProperties run.systemProperties
|
systemProperties run.systemProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,18 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
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.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -68,8 +70,9 @@ public class BrowserOverviewComp extends SimpleComp {
|
||||||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
||||||
|
|
||||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
|
var recent = ListBindingsHelper.mappedContentBinding(
|
||||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
|
model.getSavedState().getRecentDirectories(),
|
||||||
|
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
|
||||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
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.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
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.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -47,7 +47,7 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
var backgroundStack =
|
var backgroundStack =
|
||||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
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(
|
var list = new BrowserSelectionListComp(
|
||||||
binding,
|
binding,
|
||||||
entry -> Bindings.createStringBinding(
|
entry -> Bindings.createStringBinding(
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package io.xpipe.app.browser;
|
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.browser.session.BrowserSessionModel;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
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.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
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.storage.DataStorage;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -31,6 +30,9 @@ import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class BrowserWelcomeComp extends SimpleComp {
|
public class BrowserWelcomeComp extends SimpleComp {
|
||||||
|
@ -65,7 +67,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
return new VBox(hbox);
|
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());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
if (entry.isEmpty()) {
|
if (entry.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -76,7 +78,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).getList();
|
});
|
||||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||||
|
|
||||||
var headerBinding = BindingsHelper.flatMap(empty, b -> {
|
var headerBinding = BindingsHelper.flatMap(empty, b -> {
|
||||||
|
|
|
@ -24,10 +24,6 @@ public class BrowserEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BrowserIconFileType fileType(FileSystem.FileEntry rawFileEntry) {
|
private static BrowserIconFileType fileType(FileSystem.FileEntry rawFileEntry) {
|
||||||
if (rawFileEntry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawFileEntry.getKind() == FileKind.DIRECTORY) {
|
if (rawFileEntry.getKind() == FileKind.DIRECTORY) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -42,10 +38,6 @@ public class BrowserEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BrowserIconDirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
|
private static BrowserIconDirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
|
||||||
if (rawFileEntry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawFileEntry.getKind() != FileKind.DIRECTORY) {
|
if (rawFileEntry.getKind() != FileKind.DIRECTORY) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
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.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.FileReference;
|
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.FileNames;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
import io.xpipe.core.util.FailableFunction;
|
import io.xpipe.core.util.FailableFunction;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@ -38,8 +40,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var l = new DerivedObservableList<>(fileSelection, true);
|
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
|
||||||
l.bindContent(newValue.getFileList().getSelection());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
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.Size;
|
||||||
import javafx.css.SizeUnits;
|
import javafx.css.SizeUnits;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -39,13 +38,10 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
|
||||||
}))
|
}))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
List<? extends ObservableValue<Boolean>> l = cm.getItems().stream()
|
|
||||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
|
||||||
.toList();
|
|
||||||
button.visibleProperty()
|
button.visibleProperty()
|
||||||
.bind(Bindings.createBooleanBinding(() -> {
|
.bind(ListBindingsHelper.anyMatch(cm.getItems().stream()
|
||||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||||
}, l.toArray(ObservableValue[]::new)));
|
.toList()));
|
||||||
|
|
||||||
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
||||||
button.fontProperty().subscribe(c -> {
|
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.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
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 io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -88,8 +89,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listView.getChildren().equals(newShown)) {
|
if (!listView.getChildren().equals(newShown)) {
|
||||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
ListBindingsHelper.setContent(listView.getChildren(), newShown);
|
||||||
d.setContent(newShown);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ public class StoreToggleComp extends SimpleComp {
|
||||||
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
|
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
|
||||||
v -> {
|
v -> {
|
||||||
setter.accept(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() {
|
public StoreCategoryWrapper getParent() {
|
||||||
return StoreViewState.get().getCategories().getList().stream()
|
return StoreViewState.get().getCategories().stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
||||||
.findAny()
|
.findAny()
|
||||||
|
@ -122,7 +122,7 @@ public class StoreCategoryWrapper {
|
||||||
sortMode.setValue(category.getSortMode());
|
sortMode.setValue(category.getSortMode());
|
||||||
share.setValue(category.isShare());
|
share.setValue(category.isShare());
|
||||||
|
|
||||||
containedEntries.setAll(StoreViewState.get().getAllEntries().getList().stream()
|
containedEntries.setAll(StoreViewState.get().getAllEntries().stream()
|
||||||
.filter(entry -> {
|
.filter(entry -> {
|
||||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
||||||
|| (AppPrefs.get()
|
|| (AppPrefs.get()
|
||||||
|
@ -132,7 +132,7 @@ public class StoreCategoryWrapper {
|
||||||
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
||||||
})
|
})
|
||||||
.toList());
|
.toList());
|
||||||
children.setAll(StoreViewState.get().getCategories().getList().stream()
|
children.setAll(StoreViewState.get().getCategories().stream()
|
||||||
.filter(storeCategoryWrapper -> getCategory()
|
.filter(storeCategoryWrapper -> getCategory()
|
||||||
.getUuid()
|
.getUuid()
|
||||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||||
|
|
|
@ -286,6 +286,10 @@ public class StoreCreationComp extends DialogComp {
|
||||||
if (ex instanceof ValidationException) {
|
if (ex instanceof ValidationException) {
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
skippable.set(false);
|
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 {
|
} else {
|
||||||
skippable.set(true);
|
skippable.set(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,14 +372,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
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()) {
|
if (AppPrefs.get().developerMode().getValue()) {
|
||||||
var browse = new MenuItem(AppI18n.get("browseInternalStorage"), new FontIcon("mdi2f-folder-open-outline"));
|
var browse = new MenuItem(AppI18n.get("browseInternalStorage"), new FontIcon("mdi2f-folder-open-outline"));
|
||||||
browse.setOnAction(
|
browse.setOnAction(
|
||||||
|
@ -387,6 +379,26 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
contextMenu.getItems().add(browse);
|
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())) {
|
if (DataStorage.get().isRootEntry(wrapper.getEntry())) {
|
||||||
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
||||||
var none = new MenuItem("None");
|
var none = new MenuItem("None");
|
||||||
|
@ -406,72 +418,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
contextMenu.getItems().add(color);
|
contextMenu.getItems().add(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrapper.getEntry().getProvider() != null) {
|
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2n-note-text"));
|
||||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
notes.setOnAction(event -> {
|
||||||
StoreViewState.get()
|
wrapper.getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
|
||||||
.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();
|
event.consume();
|
||||||
});
|
});
|
||||||
if (storeCategoryWrapper.getParent() == null) {
|
notes.visibleProperty().bind(BindingsHelper.map(wrapper.getNotes(), s -> s.getCommited() == null));
|
||||||
m.setDisable(true);
|
contextMenu.getItems().add(notes);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
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());
|
|
||||||
|
|
||||||
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||||
del.disableProperty()
|
del.disableProperty()
|
||||||
|
|
|
@ -18,8 +18,8 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> createList() {
|
private Comp<?> createList() {
|
||||||
var content = new ListBoxViewComp<>(
|
var content = new ListBoxViewComp<>(
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList(),
|
StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList(),
|
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
|
||||||
(StoreSection e) -> {
|
(StoreSection e) -> {
|
||||||
var custom = StoreSection.customSection(e, true).hgrow();
|
var custom = StoreSection.customSection(e, true).hgrow();
|
||||||
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
|
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 showIntro = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var all = StoreViewState.get().getAllConnectionsCategory();
|
var all = StoreViewState.get().getAllConnectionsCategory();
|
||||||
var connections = StoreViewState.get().getAllEntries().getList().stream()
|
var connections = StoreViewState.get().getAllEntries().stream()
|
||||||
.filter(wrapper -> all.contains(wrapper))
|
.filter(wrapper -> all.contains(wrapper))
|
||||||
.toList();
|
.toList();
|
||||||
return initialCount == connections.size()
|
return initialCount == connections.size()
|
||||||
|
@ -45,21 +45,21 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
.getRoot()
|
.getRoot()
|
||||||
.equals(StoreViewState.get().getAllConnectionsCategory());
|
.equals(StoreViewState.get().getAllConnectionsCategory());
|
||||||
},
|
},
|
||||||
StoreViewState.get().getAllEntries().getList(),
|
StoreViewState.get().getAllEntries(),
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||||
map.put(
|
map.put(
|
||||||
createList(),
|
createList(),
|
||||||
Bindings.not(Bindings.isEmpty(
|
Bindings.not(Bindings.isEmpty(
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||||
|
|
||||||
map.put(new StoreIntroComp(), showIntro);
|
map.put(new StoreIntroComp(), showIntro);
|
||||||
map.put(
|
map.put(
|
||||||
new StoreNotFoundComp(),
|
new StoreNotFoundComp(),
|
||||||
Bindings.and(
|
Bindings.and(
|
||||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries().getList())),
|
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
|
||||||
Bindings.isEmpty(
|
Bindings.isEmpty(
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||||
return new MultiContentComp(map).createRegion();
|
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.FilterComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -55,24 +56,25 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
label.textProperty().bind(name);
|
label.textProperty().bind(name);
|
||||||
label.getStyleClass().add("name");
|
label.getStyleClass().add("name");
|
||||||
|
|
||||||
var all = StoreViewState.get().getAllEntries().filtered(
|
var all = ListBindingsHelper.filteredContentBinding(
|
||||||
|
StoreViewState.get().getAllEntries(),
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
|
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||||
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
|
return StoreViewState.get()
|
||||||
// Sadly the all binding does not update when the individual visibility of entries changes
|
.getActiveCategory()
|
||||||
// But it is good enough.
|
.getValue()
|
||||||
var showProvider = storeEntryWrapper.getEntry().getProvider() == null ||
|
.getRoot()
|
||||||
storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
|
.equals(storeRoot);
|
||||||
return inRootCategory && showProvider;
|
|
||||||
},
|
},
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
var shownList = all.filtered(
|
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||||
|
all,
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
return storeEntryWrapper.shouldShow(
|
return storeEntryWrapper.shouldShow(
|
||||||
StoreViewState.get().getFilterString().getValue());
|
StoreViewState.get().getFilterString().getValue());
|
||||||
},
|
},
|
||||||
StoreViewState.get().getFilterString());
|
StoreViewState.get().getFilterString());
|
||||||
var count = new CountComp<>(shownList.getList(), all.getList());
|
var count = new CountComp<>(shownList, all);
|
||||||
|
|
||||||
var c = count.createRegion();
|
var c = count.createRegion();
|
||||||
var topBar = new HBox(
|
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.DataStoreColor;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class StoreEntryWrapper {
|
public class StoreEntryWrapper {
|
||||||
|
@ -61,22 +65,12 @@ public class StoreEntryWrapper {
|
||||||
setupListeners();
|
setupListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Observable> getUpdateObservables() {
|
|
||||||
return List.of(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveTo(DataStoreCategory category) {
|
public void moveTo(DataStoreCategory category) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
DataStorage.get().updateCategory(entry, category);
|
DataStorage.get().updateCategory(entry, category);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orderBefore(StoreEntryWrapper other) {
|
|
||||||
ThreadHelper.runAsync(() -> {
|
|
||||||
DataStorage.get().orderBefore(getEntry(),other.getEntry());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInStorage() {
|
public boolean isInStorage() {
|
||||||
return DataStorage.get().getStoreEntries().contains(entry);
|
return DataStorage.get().getStoreEntries().contains(entry);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContextMenu createMenu() {
|
private ContextMenu createMenu() {
|
||||||
if (section.getShownChildren().getList().isEmpty()) {
|
if (section.getShownChildren().isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
||||||
var w = section.getWrapper();
|
var w = section.getWrapper();
|
||||||
var graphic =
|
var graphic =
|
||||||
w.getEntry().getProvider().getDisplayIconFileName(w.getEntry().getStore());
|
w.getEntry().getProvider().getDisplayIconFileName(w.getEntry().getStore());
|
||||||
if (c.getList().isEmpty()) {
|
if (c.isEmpty()) {
|
||||||
var item = ContextMenuHelper.item(
|
var item = ContextMenuHelper.item(
|
||||||
PrettyImageHelper.ofFixedSizeSquare(graphic, 16),
|
PrettyImageHelper.ofFixedSizeSquare(graphic, 16),
|
||||||
w.getName().getValue());
|
w.getName().getValue());
|
||||||
|
@ -55,7 +55,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = new ArrayList<MenuItem>();
|
var items = new ArrayList<MenuItem>();
|
||||||
for (StoreSection sub : c.getList()) {
|
for (StoreSection sub : c) {
|
||||||
if (!sub.getWrapper().getValidity().getValue().isUsable()) {
|
if (!sub.getWrapper().getValidity().getValue().isUsable()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,37 +2,37 @@ package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
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.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
import javafx.beans.value.ObservableStringValue;
|
import javafx.beans.value.ObservableStringValue;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.ToIntFunction;
|
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public class StoreSection {
|
public class StoreSection {
|
||||||
|
|
||||||
StoreEntryWrapper wrapper;
|
StoreEntryWrapper wrapper;
|
||||||
DerivedObservableList<StoreSection> allChildren;
|
ObservableList<StoreSection> allChildren;
|
||||||
DerivedObservableList<StoreSection> shownChildren;
|
ObservableList<StoreSection> shownChildren;
|
||||||
int depth;
|
int depth;
|
||||||
ObservableBooleanValue showDetails;
|
ObservableBooleanValue showDetails;
|
||||||
|
|
||||||
public StoreSection(
|
public StoreSection(
|
||||||
StoreEntryWrapper wrapper,
|
StoreEntryWrapper wrapper,
|
||||||
DerivedObservableList<StoreSection> allChildren,
|
ObservableList<StoreSection> allChildren,
|
||||||
DerivedObservableList<StoreSection> shownChildren,
|
ObservableList<StoreSection> shownChildren,
|
||||||
int depth) {
|
int depth) {
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
this.allChildren = allChildren;
|
this.allChildren = allChildren;
|
||||||
|
@ -41,10 +41,10 @@ public class StoreSection {
|
||||||
if (wrapper != null) {
|
if (wrapper != null) {
|
||||||
this.showDetails = Bindings.createBooleanBinding(
|
this.showDetails = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
|
return wrapper.getExpanded().get() || allChildren.isEmpty();
|
||||||
},
|
},
|
||||||
wrapper.getExpanded(),
|
wrapper.getExpanded(),
|
||||||
allChildren.getList());
|
allChildren);
|
||||||
} else {
|
} else {
|
||||||
this.showDetails = new SimpleBooleanProperty(true);
|
this.showDetails = new SimpleBooleanProperty(true);
|
||||||
}
|
}
|
||||||
|
@ -59,77 +59,51 @@ public class StoreSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DerivedObservableList<StoreSection> sorted(
|
private static ObservableList<StoreSection> sorted(
|
||||||
DerivedObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
var explicitOrderComp = Comparator.<StoreSection>comparingInt(new ToIntFunction<>() {
|
var c = Comparator.<StoreSection>comparingInt(
|
||||||
@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(
|
|
||||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
||||||
var comp = explicitOrderComp.thenComparing(usableComp);
|
|
||||||
var mappedSortMode = BindingsHelper.flatMap(
|
var mappedSortMode = BindingsHelper.flatMap(
|
||||||
category,
|
category,
|
||||||
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||||
return list.sorted((o1, o2) -> {
|
return ListBindingsHelper.orderedContentBinding(
|
||||||
|
list,
|
||||||
|
(o1, o2) -> {
|
||||||
var current = mappedSortMode.getValue();
|
var current = mappedSortMode.getValue();
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
return comp.thenComparing(current.comparator())
|
return c.thenComparing(current.comparator())
|
||||||
.compare(current.representative(o1), current.representative(o2));
|
.compare(current.representative(o1), current.representative(o2));
|
||||||
} else {
|
} else {
|
||||||
return comp.compare(o1, o2);
|
return c.compare(o1, o2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mappedSortMode,
|
mappedSortMode);
|
||||||
StoreViewState.get().getEntriesOrderChangeObservable());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StoreSection createTopLevel(
|
public static StoreSection createTopLevel(
|
||||||
DerivedObservableList<StoreEntryWrapper> all,
|
ObservableList<StoreEntryWrapper> all,
|
||||||
Predicate<StoreEntryWrapper> entryFilter,
|
Predicate<StoreEntryWrapper> entryFilter,
|
||||||
ObservableStringValue filterString,
|
ObservableStringValue filterString,
|
||||||
ObservableValue<StoreCategoryWrapper> category) {
|
ObservableValue<StoreCategoryWrapper> category) {
|
||||||
var topLevel = all.filtered(section -> {
|
var topLevel = ListBindingsHelper.filteredContentBinding(
|
||||||
|
all,
|
||||||
|
section -> {
|
||||||
return DataStorage.get().isRootEntry(section.getEntry());
|
return DataStorage.get().isRootEntry(section.getEntry());
|
||||||
},
|
},
|
||||||
category,
|
category);
|
||||||
StoreViewState.get().getEntriesListChangeObservable());
|
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||||
var cached = topLevel.mapped(
|
topLevel,
|
||||||
|
topLevel,
|
||||||
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
||||||
var ordered = sorted(cached, category);
|
var ordered = sorted(cached, category);
|
||||||
var shown = ordered.filtered(
|
var shown = ListBindingsHelper.filteredContentBinding(
|
||||||
|
ordered,
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
var matchesSelector = section.anyMatches(entryFilter);
|
var matchesSelector = section.anyMatches(entryFilter);
|
||||||
var sameCategory = category == null
|
var sameCategory = category == null
|
||||||
|| category.getValue() == null
|
|| category.getValue() == null
|
||||||
|
@ -144,17 +118,15 @@ public class StoreSection {
|
||||||
private static StoreSection create(
|
private static StoreSection create(
|
||||||
StoreEntryWrapper e,
|
StoreEntryWrapper e,
|
||||||
int depth,
|
int depth,
|
||||||
DerivedObservableList<StoreEntryWrapper> all,
|
ObservableList<StoreEntryWrapper> all,
|
||||||
Predicate<StoreEntryWrapper> entryFilter,
|
Predicate<StoreEntryWrapper> entryFilter,
|
||||||
ObservableStringValue filterString,
|
ObservableStringValue filterString,
|
||||||
ObservableValue<StoreCategoryWrapper> category) {
|
ObservableValue<StoreCategoryWrapper> category) {
|
||||||
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||||
return new StoreSection(e, new DerivedObservableList<>(
|
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
|
||||||
FXCollections.observableArrayList(), true), new DerivedObservableList<>(
|
|
||||||
FXCollections.observableArrayList(), true), depth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var allChildren = all.filtered(other -> {
|
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> {
|
||||||
// Legacy implementation that does not use children caches. Use for testing
|
// Legacy implementation that does not use children caches. Use for testing
|
||||||
// if (true) return DataStorage.get()
|
// if (true) return DataStorage.get()
|
||||||
// .getDisplayParent(other.getEntry())
|
// .getDisplayParent(other.getEntry())
|
||||||
|
@ -162,35 +134,29 @@ public class StoreSection {
|
||||||
// .orElse(false);
|
// .orElse(false);
|
||||||
|
|
||||||
// This check is fast as the children are cached in the storage
|
// This check is fast as the children are cached in the storage
|
||||||
var isChildren = DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||||
var showProvider = other.getEntry().getProvider() == null ||
|
});
|
||||||
other.getEntry().getProvider().shouldShow(other);
|
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||||
return isChildren && showProvider;
|
allChildren,
|
||||||
}, e.getPersistentState(), e.getCache(), StoreViewState.get().getEntriesListChangeObservable());
|
allChildren,
|
||||||
var cached = allChildren.mapped(
|
|
||||||
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
||||||
var ordered = sorted(cached, category);
|
var ordered = sorted(cached, category);
|
||||||
var filtered = ordered.filtered(
|
var filtered = ListBindingsHelper.filteredContentBinding(
|
||||||
|
ordered,
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
var matchesSelector = section.anyMatches(entryFilter);
|
var matchesSelector = section.anyMatches(entryFilter);
|
||||||
// Prevent updates for children on category switching by checking depth
|
var sameCategory = category == null
|
||||||
var showCategory = category == null
|
|
||||||
|| category.getValue() == null
|
|| category.getValue() == null
|
||||||
|| showInCategory(category.getValue(), section.getWrapper())
|
|| showInCategory(category.getValue(), section.getWrapper());
|
||||||
|| depth > 0;
|
|
||||||
// If this entry is already shown as root due to a different category than parent, don't show it
|
// If this entry is already shown as root due to a different category than parent, don't show it
|
||||||
// again here
|
// again here
|
||||||
var notRoot =
|
var notRoot =
|
||||||
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
||||||
var showProvider = section.getWrapper().getEntry().getProvider() == null ||
|
return showFilter && matchesSelector && sameCategory && notRoot;
|
||||||
section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper());
|
|
||||||
return showFilter && matchesSelector && showCategory && notRoot && showProvider;
|
|
||||||
},
|
},
|
||||||
category,
|
category,
|
||||||
filterString,
|
filterString);
|
||||||
e.getPersistentState(),
|
|
||||||
e.getCache());
|
|
||||||
return new StoreSection(e, cached, filtered, depth);
|
return new StoreSection(e, cached, filtered, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,13 +179,13 @@ public class StoreSection {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matchesFilter(String filter) {
|
public boolean shouldShow(String filter) {
|
||||||
return anyMatches(storeEntryWrapper -> storeEntryWrapper.shouldShow(filter));
|
return anyMatches(storeEntryWrapper -> storeEntryWrapper.shouldShow(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
|
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
|
||||||
return c == null
|
return c == null
|
||||||
|| c.test(wrapper)
|
|| 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.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.storage.DataStoreColor;
|
import io.xpipe.app.storage.DataStoreColor;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -40,9 +42,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
||||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getShownChildren().getList().isEmpty();
|
return section.getShownChildren().isEmpty();
|
||||||
},
|
},
|
||||||
section.getShownChildren().getList());
|
section.getShownChildren());
|
||||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
w.executeDefaultAction();
|
w.executeDefaultAction();
|
||||||
|
@ -69,11 +71,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
var expandButton = new IconButtonComp(
|
var expandButton = new IconButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
() -> section.getWrapper().getExpanded().get()
|
() -> section.getWrapper().getExpanded().get()
|
||||||
&& section.getShownChildren().getList().size() > 0
|
&& section.getShownChildren().size() > 0
|
||||||
? "mdal-keyboard_arrow_down"
|
? "mdal-keyboard_arrow_down"
|
||||||
: "mdal-keyboard_arrow_right",
|
: "mdal-keyboard_arrow_right",
|
||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getShownChildren().getList()),
|
section.getShownChildren()),
|
||||||
() -> {
|
() -> {
|
||||||
section.getWrapper().toggleExpanded();
|
section.getWrapper().toggleExpanded();
|
||||||
});
|
});
|
||||||
|
@ -87,7 +89,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
return "Expand " + section.getWrapper().getName().getValue();
|
return "Expand " + section.getWrapper().getName().getValue();
|
||||||
},
|
},
|
||||||
section.getWrapper().getName()))
|
section.getWrapper().getName()))
|
||||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||||
.styleClass("expand-button")
|
.styleClass("expand-button")
|
||||||
.maxHeight(100)
|
.maxHeight(100)
|
||||||
.vgrow();
|
.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
|
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||||
// section is actually expanded
|
// section is actually expanded
|
||||||
var listSections = section.getShownChildren().filtered(
|
var listSections = ListBindingsHelper.filteredContentBinding(
|
||||||
storeSection -> section.getAllChildren().getList().size() <= 20
|
section.getShownChildren(),
|
||||||
|
storeSection -> section.getAllChildren().size() <= 20
|
||||||
|| section.getWrapper().getExpanded().get(),
|
|| section.getWrapper().getExpanded().get(),
|
||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getAllChildren().getList());
|
section.getAllChildren());
|
||||||
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
|
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||||
})
|
})
|
||||||
.minHeight(0)
|
.minHeight(0)
|
||||||
|
@ -140,10 +143,10 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
var expanded = Bindings.createBooleanBinding(
|
var expanded = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getWrapper().getExpanded().get()
|
return section.getWrapper().getExpanded().get()
|
||||||
&& section.getShownChildren().getList().size() > 0;
|
&& section.getShownChildren().size() > 0;
|
||||||
},
|
},
|
||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getShownChildren().getList());
|
section.getShownChildren());
|
||||||
var full = new VerticalComp(List.of(
|
var full = new VerticalComp(List.of(
|
||||||
topEntryList,
|
topEntryList,
|
||||||
Comp.separator().hide(expanded.not()),
|
Comp.separator().hide(expanded.not()),
|
||||||
|
@ -152,7 +155,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
.apply(struc -> struc.get().setFillHeight(true))
|
.apply(struc -> struc.get().setFillHeight(true))
|
||||||
.hide(Bindings.or(
|
.hide(Bindings.or(
|
||||||
Bindings.not(section.getWrapper().getExpanded()),
|
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")
|
return full.styleClass("store-entry-section-comp")
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get().setFillWidth(true);
|
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.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.storage.DataStoreColor;
|
import io.xpipe.app.storage.DataStoreColor;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -82,7 +84,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
expanded =
|
expanded =
|
||||||
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
||||||
&& section.getShownChildren().getList().size() > 0);
|
&& section.getShownChildren().size() > 0);
|
||||||
var button = new IconButtonComp(
|
var button = new IconButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
() -> expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right",
|
() -> 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().getValue();
|
||||||
},
|
},
|
||||||
section.getWrapper().getName()))
|
section.getWrapper().getName()))
|
||||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||||
.grow(false, true)
|
.grow(false, true)
|
||||||
.styleClass("expand-button");
|
.styleClass("expand-button");
|
||||||
|
|
||||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getShownChildren().getList().isEmpty();
|
return section.getShownChildren().isEmpty();
|
||||||
},
|
},
|
||||||
section.getShownChildren().getList());
|
section.getShownChildren());
|
||||||
Consumer<StoreEntryWrapper> quickAccessAction = action;
|
Consumer<StoreEntryWrapper> quickAccessAction = action;
|
||||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||||
.vgrow()
|
.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
|
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||||
// section is actually expanded
|
// section is actually expanded
|
||||||
var listSections = section.getWrapper() != null
|
var listSections = section.getWrapper() != null
|
||||||
? section.getShownChildren().filtered(
|
? ListBindingsHelper.filteredContentBinding(
|
||||||
storeSection -> section.getAllChildren().getList().size() <= 20 || expanded.get(),
|
section.getShownChildren(),
|
||||||
|
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||||
expanded,
|
expanded,
|
||||||
section.getAllChildren().getList())
|
section.getAllChildren())
|
||||||
: section.getShownChildren();
|
: 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);
|
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
|
||||||
})
|
})
|
||||||
.minHeight(0)
|
.minHeight(0)
|
||||||
|
@ -145,7 +148,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
.apply(struc -> struc.get().setFillHeight(true))
|
.apply(struc -> struc.get().setFillHeight(true))
|
||||||
.hide(Bindings.or(
|
.hide(Bindings.or(
|
||||||
Bindings.not(expanded),
|
Bindings.not(expanded),
|
||||||
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
|
Bindings.size(section.getAllChildren()).isEqualTo(0))));
|
||||||
|
|
||||||
var vert = new VerticalComp(list);
|
var vert = new VerticalComp(list);
|
||||||
if (condensedStyle) {
|
if (condensedStyle) {
|
||||||
|
|
|
@ -48,7 +48,7 @@ public interface StoreSortMode {
|
||||||
@Override
|
@Override
|
||||||
public StoreSection representative(StoreSection s) {
|
public StoreSection representative(StoreSection s) {
|
||||||
return Stream.concat(
|
return Stream.concat(
|
||||||
s.getShownChildren().getList().stream()
|
s.getShownChildren().stream()
|
||||||
.filter(section -> section.getWrapper()
|
.filter(section -> section.getWrapper()
|
||||||
.getEntry()
|
.getEntry()
|
||||||
.getValidity()
|
.getValidity()
|
||||||
|
@ -76,7 +76,7 @@ public interface StoreSortMode {
|
||||||
@Override
|
@Override
|
||||||
public StoreSection representative(StoreSection s) {
|
public StoreSection representative(StoreSection s) {
|
||||||
return Stream.concat(
|
return Stream.concat(
|
||||||
s.getShownChildren().getList().stream()
|
s.getShownChildren().stream()
|
||||||
.filter(section -> section.getWrapper()
|
.filter(section -> section.getWrapper()
|
||||||
.getEntry()
|
.getEntry()
|
||||||
.getValidity()
|
.getValidity()
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
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.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.StorageListener;
|
import io.xpipe.app.storage.StorageListener;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
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.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -23,18 +29,12 @@ public class StoreViewState {
|
||||||
private final StringProperty filter = new SimpleStringProperty();
|
private final StringProperty filter = new SimpleStringProperty();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final DerivedObservableList<StoreEntryWrapper> allEntries =
|
private final ObservableList<StoreEntryWrapper> allEntries =
|
||||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final DerivedObservableList<StoreCategoryWrapper> categories =
|
private final ObservableList<StoreCategoryWrapper> categories =
|
||||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final IntegerProperty entriesOrderChangeObservable = new SimpleIntegerProperty();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final IntegerProperty entriesListChangeObservable = new SimpleIntegerProperty();
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
||||||
|
@ -76,8 +76,8 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateContent() {
|
private void updateContent() {
|
||||||
categories.getList().forEach(c -> c.update());
|
categories.forEach(c -> c.update());
|
||||||
allEntries.getList().forEach(e -> e.update());
|
allEntries.forEach(e -> e.update());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSections() {
|
private void initSections() {
|
||||||
|
@ -86,19 +86,16 @@ public class StoreViewState {
|
||||||
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
|
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
currentTopLevelSection =
|
currentTopLevelSection =
|
||||||
new StoreSection(null,
|
new StoreSection(null, FXCollections.emptyObservableList(), FXCollections.emptyObservableList(), 0);
|
||||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
|
||||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
|
||||||
0);
|
|
||||||
ErrorEvent.fromThrowable(exception).handle();
|
ErrorEvent.fromThrowable(exception).handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initContent() {
|
private void initContent() {
|
||||||
allEntries.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||||
.map(StoreEntryWrapper::new)
|
.map(StoreEntryWrapper::new)
|
||||||
.toList()));
|
.toList()));
|
||||||
categories.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
categories.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
||||||
.map(StoreCategoryWrapper::new)
|
.map(StoreCategoryWrapper::new)
|
||||||
.toList()));
|
.toList()));
|
||||||
|
|
||||||
|
@ -106,11 +103,11 @@ public class StoreViewState {
|
||||||
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
||||||
});
|
});
|
||||||
var selected = AppCache.get("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
var selected = AppCache.get("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
||||||
activeCategory.setValue(categories.getList().stream()
|
activeCategory.setValue(categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(categories.getList().stream()
|
.orElse(categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.DEFAULT_CATEGORY_UUID))
|
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.DEFAULT_CATEGORY_UUID))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
@ -122,9 +119,9 @@ public class StoreViewState {
|
||||||
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
|
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
var l = new ArrayList<>(allEntries.getList());
|
var l = new ArrayList<>(allEntries);
|
||||||
allEntries.getList().clear();
|
allEntries.clear();
|
||||||
allEntries.getList().setAll(l);
|
allEntries.setAll(l);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -132,21 +129,6 @@ public class StoreViewState {
|
||||||
|
|
||||||
// Watch out for synchronizing all calls to the entries and categories list!
|
// Watch out for synchronizing all calls to the entries and categories list!
|
||||||
DataStorage.get().addListener(new StorageListener() {
|
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
|
@Override
|
||||||
public void onStoreAdd(DataStoreEntry... entry) {
|
public void onStoreAdd(DataStoreEntry... entry) {
|
||||||
var l = Arrays.stream(entry)
|
var l = Arrays.stream(entry)
|
||||||
|
@ -160,11 +142,11 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
allEntries.getList().addAll(l);
|
allEntries.addAll(l);
|
||||||
}
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
categories.getList().stream()
|
categories.stream()
|
||||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||||
.getEntry()
|
.getEntry()
|
||||||
.getCategoryUuid()
|
.getCategoryUuid()
|
||||||
|
@ -181,14 +163,14 @@ public class StoreViewState {
|
||||||
var a = Arrays.stream(entry).collect(Collectors.toSet());
|
var a = Arrays.stream(entry).collect(Collectors.toSet());
|
||||||
List<StoreEntryWrapper> l;
|
List<StoreEntryWrapper> l;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
l = allEntries.getList().stream()
|
l = allEntries.stream()
|
||||||
.filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry()))
|
.filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry()))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
List<StoreCategoryWrapper> cats;
|
List<StoreCategoryWrapper> cats;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
cats = categories.getList().stream()
|
cats = categories.stream()
|
||||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||||
.getEntry()
|
.getEntry()
|
||||||
.getCategoryUuid()
|
.getCategoryUuid()
|
||||||
|
@ -204,7 +186,7 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
allEntries.getList().removeAll(l);
|
allEntries.removeAll(l);
|
||||||
}
|
}
|
||||||
cats.forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
cats.forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
||||||
});
|
});
|
||||||
|
@ -221,7 +203,7 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
categories.getList().add(l);
|
categories.add(l);
|
||||||
}
|
}
|
||||||
l.update();
|
l.update();
|
||||||
});
|
});
|
||||||
|
@ -231,7 +213,7 @@ public class StoreViewState {
|
||||||
public void onCategoryRemove(DataStoreCategory category) {
|
public void onCategoryRemove(DataStoreCategory category) {
|
||||||
Optional<StoreCategoryWrapper> found;
|
Optional<StoreCategoryWrapper> found;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
found = categories.getList().stream()
|
found = categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().equals(category))
|
storeCategoryWrapper.getCategory().equals(category))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
@ -247,7 +229,7 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
categories.getList().remove(found.get());
|
categories.remove(found.get());
|
||||||
}
|
}
|
||||||
var p = found.get().getParent();
|
var p = found.get().getParent();
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
|
@ -258,34 +240,15 @@ public class StoreViewState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
|
public ObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
|
||||||
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) {
|
|
||||||
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
|
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
|
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
|
||||||
var o1Root = o1.getRoot();
|
var o1Root = o1.getRoot();
|
||||||
var o2Root = o2.getRoot();
|
var o2Root = o2.getRoot();
|
||||||
|
|
||||||
if (o1Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
if (o1Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o2Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
if (o2Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +265,22 @@ public class StoreViewState {
|
||||||
return 1;
|
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());
|
var parent = compare(o1.getParent(), o2.getParent());
|
||||||
if (parent != 0) {
|
if (parent != 0) {
|
||||||
return parent;
|
return parent;
|
||||||
|
@ -312,11 +291,13 @@ public class StoreViewState {
|
||||||
.compareToIgnoreCase(o2.nameProperty().getValue());
|
.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() {
|
public StoreCategoryWrapper getAllConnectionsCategory() {
|
||||||
return categories.getList().stream()
|
return categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
|
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
@ -324,7 +305,7 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreCategoryWrapper getAllScriptsCategory() {
|
public StoreCategoryWrapper getAllScriptsCategory() {
|
||||||
return categories.getList().stream()
|
return categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
|
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
@ -332,14 +313,14 @@ public class StoreViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
||||||
return allEntries.getList().stream()
|
return allEntries.stream()
|
||||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
|
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
|
||||||
return categories.getList().stream()
|
return categories.stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().equals(entry))
|
storeCategoryWrapper.getCategory().equals(entry))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
|
@ -28,10 +28,6 @@ import java.util.List;
|
||||||
|
|
||||||
public interface DataStoreProvider {
|
public interface DataStoreProvider {
|
||||||
|
|
||||||
default boolean shouldShow(StoreEntryWrapper w) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
|
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
|
||||||
return new SimpleBooleanProperty(false);
|
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.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.util.Translatable;
|
import io.xpipe.app.util.Translatable;
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ -76,7 +79,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
||||||
list.add(null);
|
list.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
cb.getItems().setAll(list);
|
ListBindingsHelper.setContent(cb.getItems(), list);
|
||||||
});
|
});
|
||||||
|
|
||||||
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
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.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
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.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
|
@ -25,6 +26,7 @@ import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
@ -77,12 +79,13 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
showing.bind(cm.showingProperty());
|
showing.bind(cm.showingProperty());
|
||||||
return cm;
|
return cm;
|
||||||
}));
|
}));
|
||||||
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
|
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||||
|
category.getContainedEntries(),
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
return storeEntryWrapper.shouldShow(
|
return storeEntryWrapper.shouldShow(
|
||||||
StoreViewState.get().getFilterString().getValue());
|
StoreViewState.get().getFilterString().getValue());
|
||||||
},
|
},
|
||||||
StoreViewState.get().getFilterString()).getList();
|
StoreViewState.get().getFilterString());
|
||||||
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
|
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
|
||||||
var hover = new SimpleBooleanProperty();
|
var hover = new SimpleBooleanProperty();
|
||||||
var focus = 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,10 +45,11 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
try (ShellControl pc = LocalShell.getShell().start()) {
|
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",
|
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications",
|
||||||
applicationName))
|
applicationName))
|
||||||
.executeAndCheck();
|
.readStdoutIfPossible();
|
||||||
|
return out.isPresent() && !out.get().isBlank();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -194,10 +194,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(Path file) throws Exception {
|
public void launch(Path file) throws Exception {
|
||||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
.add("open", "-a")
|
sc.executeSimpleCommand(CommandBuilder.of()
|
||||||
.addQuoted(applicationName)
|
.add("open", "-a").addQuoted(applicationName).addFile(file.toString()));
|
||||||
.addFile(file.toString()));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -328,16 +328,16 @@ public abstract class DataStorage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.setCategoryUuid(newCategory.getUuid());
|
|
||||||
var children = getDeepStoreChildren(entry);
|
var children = getDeepStoreChildren(entry);
|
||||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||||
listeners.forEach(storageListener -> storageListener.onStoreListUpdate());
|
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||||
saveAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void orderBefore(DataStoreEntry entry, DataStoreEntry reference) {
|
entry.setCategoryUuid(newCategory.getUuid());
|
||||||
entry.setOrderBefore(reference != null ? reference.getUuid() : null);
|
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||||
listeners.forEach(storageListener -> storageListener.onStoreOrderUpdate());
|
|
||||||
|
var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||||
|
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||||
|
saveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean refreshChildren(DataStoreEntry e) {
|
public boolean refreshChildren(DataStoreEntry e) {
|
||||||
|
@ -439,8 +439,8 @@ public abstract class DataStorage {
|
||||||
pair.getKey().setStoreInternal(merged, false);
|
pair.getKey().setStoreInternal(merged, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = pair.getKey().getStorePersistentState();
|
var mergedState = pair.getKey().getStorePersistentState().deepCopy();
|
||||||
var mergedState = s.mergeCopy(pair.getValue().get().getStorePersistentState());
|
mergedState.merge(pair.getValue().get().getStorePersistentState());
|
||||||
pair.getKey().setStorePersistentState(mergedState);
|
pair.getKey().setStorePersistentState(mergedState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -788,7 +788,9 @@ public abstract class DataStorage {
|
||||||
|
|
||||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
|
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
|
||||||
return storeEntriesSet.stream()
|
return storeEntriesSet.stream()
|
||||||
.filter(n -> n.getStore() == store || (!identityOnly && (n.getStore() != null
|
.filter(n -> n.getStore() == store
|
||||||
|
|| (!identityOnly
|
||||||
|
&& (n.getStore() != null
|
||||||
&& Objects.equals(
|
&& Objects.equals(
|
||||||
store.getClass(), n.getStore().getClass())
|
store.getClass(), n.getStore().getClass())
|
||||||
&& store.equals(n.getStore()))))
|
&& store.equals(n.getStore()))))
|
||||||
|
|
|
@ -72,9 +72,6 @@ public class DataStoreEntry extends StorageElement {
|
||||||
@NonFinal
|
@NonFinal
|
||||||
String notes;
|
String notes;
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
UUID orderBefore;
|
|
||||||
|
|
||||||
private DataStoreEntry(
|
private DataStoreEntry(
|
||||||
Path directory,
|
Path directory,
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
|
@ -89,8 +86,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
JsonNode storePersistentState,
|
JsonNode storePersistentState,
|
||||||
boolean expanded,
|
boolean expanded,
|
||||||
DataStoreColor color,
|
DataStoreColor color,
|
||||||
String notes, UUID orderBefore
|
String notes) {
|
||||||
) {
|
|
||||||
super(directory, uuid, name, lastUsed, lastModified, dirty);
|
super(directory, uuid, name, lastUsed, lastModified, dirty);
|
||||||
this.categoryUuid = categoryUuid;
|
this.categoryUuid = categoryUuid;
|
||||||
this.store = DataStorageParser.storeFromNode(storeNode);
|
this.store = DataStorageParser.storeFromNode(storeNode);
|
||||||
|
@ -99,7 +95,6 @@ public class DataStoreEntry extends StorageElement {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.expanded = expanded;
|
this.expanded = expanded;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.orderBefore = orderBefore;
|
|
||||||
this.provider = store != null
|
this.provider = store != null
|
||||||
? DataStoreProviders.byStoreClass(store.getClass()).orElse(null)
|
? DataStoreProviders.byStoreClass(store.getClass()).orElse(null)
|
||||||
: null;
|
: null;
|
||||||
|
@ -114,12 +109,10 @@ public class DataStoreEntry extends StorageElement {
|
||||||
String name,
|
String name,
|
||||||
Instant lastUsed,
|
Instant lastUsed,
|
||||||
Instant lastModified,
|
Instant lastModified,
|
||||||
DataStore store, UUID orderBefore
|
DataStore store) {
|
||||||
) {
|
|
||||||
super(directory, uuid, name, lastUsed, lastModified, false);
|
super(directory, uuid, name, lastUsed, lastModified, false);
|
||||||
this.categoryUuid = categoryUuid;
|
this.categoryUuid = categoryUuid;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.orderBefore = orderBefore;
|
|
||||||
this.storeNode = null;
|
this.storeNode = null;
|
||||||
this.validity = Validity.INCOMPLETE;
|
this.validity = Validity.INCOMPLETE;
|
||||||
this.configuration = Configuration.defaultConfiguration();
|
this.configuration = Configuration.defaultConfiguration();
|
||||||
|
@ -137,8 +130,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
Instant.now(),
|
Instant.now(),
|
||||||
Instant.now(),
|
Instant.now(),
|
||||||
store,
|
store);
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
|
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
|
||||||
|
@ -167,7 +159,6 @@ public class DataStoreEntry extends StorageElement {
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null);
|
null);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -185,8 +176,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
JsonNode storePersistentState,
|
JsonNode storePersistentState,
|
||||||
boolean expanded,
|
boolean expanded,
|
||||||
DataStoreColor color,
|
DataStoreColor color,
|
||||||
String notes,
|
String notes) {
|
||||||
UUID orderBeforeEntry) {
|
|
||||||
return new DataStoreEntry(
|
return new DataStoreEntry(
|
||||||
directory,
|
directory,
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -201,8 +191,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
storePersistentState,
|
storePersistentState,
|
||||||
expanded,
|
expanded,
|
||||||
color,
|
color,
|
||||||
notes,
|
notes);
|
||||||
orderBeforeEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<DataStoreEntry> fromDirectory(Path dir) throws Exception {
|
public static Optional<DataStoreEntry> fromDirectory(Path dir) throws Exception {
|
||||||
|
@ -237,15 +226,6 @@ public class DataStoreEntry extends StorageElement {
|
||||||
.map(jsonNode -> jsonNode.textValue())
|
.map(jsonNode -> jsonNode.textValue())
|
||||||
.map(Instant::parse)
|
.map(Instant::parse)
|
||||||
.orElse(Instant.EPOCH);
|
.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"))
|
var configuration = Optional.ofNullable(json.get("configuration"))
|
||||||
.map(node -> {
|
.map(node -> {
|
||||||
try {
|
try {
|
||||||
|
@ -295,19 +275,10 @@ public class DataStoreEntry extends StorageElement {
|
||||||
persistentState,
|
persistentState,
|
||||||
expanded,
|
expanded,
|
||||||
color,
|
color,
|
||||||
notes,
|
notes
|
||||||
order
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrderBefore(UUID uuid) {
|
|
||||||
var changed = !Objects.equals(orderBefore, uuid);
|
|
||||||
this.orderBefore = uuid;
|
|
||||||
if (changed) {
|
|
||||||
notifyUpdate(false, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getUuid().hashCode();
|
return getUuid().hashCode();
|
||||||
|
@ -359,7 +330,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
storePersistentStateNode = JacksonMapper.getDefault().valueToTree(storePersistentState);
|
storePersistentStateNode = JacksonMapper.getDefault().valueToTree(storePersistentState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (T) storePersistentState;
|
return (T) sds.getStateClass().cast(storePersistentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStorePersistentState(DataStoreState value) {
|
public void setStorePersistentState(DataStoreState value) {
|
||||||
|
@ -408,7 +379,6 @@ public class DataStoreEntry extends StorageElement {
|
||||||
stateObj.set("persistentState", storePersistentStateNode);
|
stateObj.set("persistentState", storePersistentStateNode);
|
||||||
obj.set("configuration", mapper.valueToTree(configuration));
|
obj.set("configuration", mapper.valueToTree(configuration));
|
||||||
stateObj.put("expanded", expanded);
|
stateObj.put("expanded", expanded);
|
||||||
stateObj.put("orderBefore", orderBefore != null ? orderBefore.toString() : null);
|
|
||||||
|
|
||||||
var entryString = mapper.writeValueAsString(obj);
|
var entryString = mapper.writeValueAsString(obj);
|
||||||
var stateString = mapper.writeValueAsString(stateObj);
|
var stateString = mapper.writeValueAsString(stateObj);
|
||||||
|
|
|
@ -2,10 +2,6 @@ package io.xpipe.app.storage;
|
||||||
|
|
||||||
public interface StorageListener {
|
public interface StorageListener {
|
||||||
|
|
||||||
void onStoreOrderUpdate();
|
|
||||||
|
|
||||||
void onStoreListUpdate();
|
|
||||||
|
|
||||||
void onStoreAdd(DataStoreEntry... entry);
|
void onStoreAdd(DataStoreEntry... entry);
|
||||||
|
|
||||||
void onStoreRemove(DataStoreEntry... entry);
|
void onStoreRemove(DataStoreEntry... entry);
|
||||||
|
|
|
@ -514,17 +514,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
try (ShellControl pc = LocalShell.getShell()) {
|
LocalShell.getShell()
|
||||||
var suffix = "\"" + configuration.getScriptFile().toString().replaceAll("\"", "\\\\\"") + "\"";
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
pc.osascriptCommand(String.format(
|
.add("open", "-a")
|
||||||
"""
|
.addQuoted("Terminal.app")
|
||||||
activate application "Terminal"
|
.addFile(configuration.getScriptFile()));
|
||||||
delay 1
|
|
||||||
tell app "Terminal" to do script %s
|
|
||||||
""",
|
|
||||||
suffix))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
||||||
|
@ -550,26 +544,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
try (ShellControl pc = LocalShell.getShell()) {
|
LocalShell.getShell()
|
||||||
pc.osascriptCommand(String.format(
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
"""
|
.add("open", "-a")
|
||||||
if application "iTerm" is not running then
|
.addQuoted("iTerm.app")
|
||||||
launch application "iTerm"
|
.addFile(configuration.getScriptFile()));
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
|
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package io.xpipe.app.terminal;
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||||
|
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.app.util.WindowsRegistry;
|
import io.xpipe.app.util.WindowsRegistry;
|
||||||
import io.xpipe.core.process.CommandBuilder;
|
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.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -26,7 +31,7 @@ public interface WezTerminalType extends ExternalTerminalType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean isRecommended() {
|
default boolean isRecommended() {
|
||||||
return false;
|
return OsType.getLocal() != OsType.WINDOWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,25 +56,62 @@ public interface WezTerminalType extends ExternalTerminalType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<Path> determineInstallation() {
|
protected Optional<Path> determineInstallation() {
|
||||||
Optional<String> launcherDir;
|
try {
|
||||||
launcherDir = WindowsRegistry.local().readValue(
|
var foundKey = WindowsRegistry.local().findKeyForEqualValueMatchRecursive(WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||||
WindowsRegistry.HKEY_LOCAL_MACHINE,
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "http://wezfurlong.org/wezterm");
|
||||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1",
|
if (foundKey.isPresent()) {
|
||||||
"InstallLocation")
|
var installKey = WindowsRegistry.local().readValue(
|
||||||
.map(p -> p + "\\wezterm-gui.exe");
|
foundKey.get().getHkey(),
|
||||||
return launcherDir.map(Path::of);
|
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() {
|
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
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
|
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
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
var path = LocalShell.getShell()
|
try (var sc = LocalShell.getShell()) {
|
||||||
.command(String.format(
|
var path = sc.command(
|
||||||
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
String.format("mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
||||||
applicationName))
|
applicationName)).readStdoutOrThrow();
|
||||||
.readStdoutOrThrow();
|
var spawn = sc.command(CommandBuilder.of().addFile(Path.of(path)
|
||||||
var c = CommandBuilder.of()
|
.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)
|
.addFile(Path.of(path)
|
||||||
.resolve("Contents")
|
.resolve("Contents")
|
||||||
.resolve("MacOS")
|
.resolve("MacOS")
|
||||||
.resolve("wezterm-gui")
|
.resolve("wezterm-gui").toString())
|
||||||
.toString())
|
|
||||||
.add("start")
|
.add("start")
|
||||||
.add(configuration.getDialectLaunchCommand());
|
.addFile(configuration.getScriptFile()));
|
||||||
ExternalApplicationHelper.startAsync(c);
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
|
||||||
value.setValue(newValue);
|
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.setValue(value.getValue());
|
||||||
box.valueProperty().addListener((observable, oldValue, newValue) -> {
|
box.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
value.setValue(newValue);
|
value.setValue(newValue);
|
||||||
|
|
|
@ -28,10 +28,9 @@ public class FileOpener {
|
||||||
try {
|
try {
|
||||||
editor.launch(Path.of(localFile).toRealPath());
|
editor.launch(Path.of(localFile).toRealPath());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e)
|
ErrorEvent.fromThrowable("Unable to launch editor "
|
||||||
.description("Unable to launch editor "
|
|
||||||
+ editor.toTranslatedString().getValue()
|
+ 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()
|
.expected()
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
|
@ -52,8 +51,7 @@ public class FileOpener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e)
|
ErrorEvent.fromThrowable("Unable to open file " + localFile, e)
|
||||||
.description("Unable to open file " + localFile)
|
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,8 +66,7 @@ public class FileOpener {
|
||||||
pc.executeSimpleCommand("open \"" + localFile + "\"");
|
pc.executeSimpleCommand("open \"" + localFile + "\"");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e)
|
ErrorEvent.fromThrowable("Unable to open file " + localFile, e)
|
||||||
.description("Unable to open file " + localFile)
|
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,6 @@ project.ext {
|
||||||
arch = getArchName()
|
arch = getArchName()
|
||||||
privateExtensions = file("$rootDir/private_extensions.txt").exists() ? file("$rootDir/private_extensions.txt").readLines() : []
|
privateExtensions = file("$rootDir/private_extensions.txt").exists() ? file("$rootDir/private_extensions.txt").readLines() : []
|
||||||
isFullRelease = System.getenv('RELEASE') != null && Boolean.parseBoolean(System.getenv('RELEASE'))
|
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'))
|
isStage = System.getenv('STAGE') != null && Boolean.parseBoolean(System.getenv('STAGE'))
|
||||||
rawVersion = file('version').text.trim()
|
rawVersion = file('version').text.trim()
|
||||||
versionString = rawVersion + (isFullRelease || isStage ? '' : '-SNAPSHOT')
|
versionString = rawVersion + (isFullRelease || isStage ? '' : '-SNAPSHOT')
|
||||||
|
@ -106,7 +105,7 @@ project.ext {
|
||||||
website = 'https://xpipe.io'
|
website = 'https://xpipe.io'
|
||||||
sourceWebsite = isStage ? 'https://github.com/xpipe-io/xpipe-ptb' : 'https://github.com/xpipe-io/xpipe'
|
sourceWebsite = isStage ? 'https://github.com/xpipe-io/xpipe-ptb' : 'https://github.com/xpipe-io/xpipe'
|
||||||
authors = 'Christopher Schnick'
|
authors = 'Christopher Schnick'
|
||||||
javafxVersion = '22.0.1'
|
javafxVersion = '23-ea+18'
|
||||||
platformName = getPlatformName()
|
platformName = getPlatformName()
|
||||||
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
||||||
jvmRunArgs = [
|
jvmRunArgs = [
|
||||||
|
@ -159,6 +158,11 @@ if (isFullRelease && rawVersion.contains("-")) {
|
||||||
throw new IllegalArgumentException("Releases must have canonical versions")
|
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 replaceVariablesInFileAsString(String f, Map<String, String> replacements) {
|
||||||
def fileName = file(f).getName()
|
def fileName = file(f).getName()
|
||||||
def text = file(f).text
|
def text = file(f).text
|
||||||
|
|
|
@ -58,13 +58,12 @@ public interface ShellControl extends ProcessControl {
|
||||||
|
|
||||||
default <T extends ShellStoreState> ShellControl withShellStateInit(StatefulDataStore<T> store) {
|
default <T extends ShellStoreState> ShellControl withShellStateInit(StatefulDataStore<T> store) {
|
||||||
return onInit(shellControl -> {
|
return onInit(shellControl -> {
|
||||||
var s = store.getState().toBuilder()
|
var s = store.getState();
|
||||||
.osType(shellControl.getOsType())
|
s.setOsType(shellControl.getOsType());
|
||||||
.shellDialect(shellControl.getOriginalShellDialect())
|
s.setShellDialect(shellControl.getOriginalShellDialect());
|
||||||
.running(true)
|
s.setRunning(true);
|
||||||
.osName(shellControl.getOsName())
|
s.setOsName(shellControl.getOsName());
|
||||||
.build();
|
store.setState(s);
|
||||||
store.setState(s.asNeeded());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +74,9 @@ public interface ShellControl extends ProcessControl {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = store.getState().toBuilder().running(false).build();
|
var s = store.getState();
|
||||||
store.setState(s.asNeeded());
|
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;
|
package io.xpipe.core.process;
|
||||||
|
|
||||||
import io.xpipe.core.store.DataStoreState;
|
import io.xpipe.core.store.DataStoreState;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
@EqualsAndHashCode(callSuper=true)
|
|
||||||
@SuperBuilder(toBuilder = true)
|
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
|
@SuperBuilder
|
||||||
public class ShellStoreState extends DataStoreState implements OsNameState {
|
public class ShellStoreState extends DataStoreState implements OsNameState {
|
||||||
|
|
||||||
OsType.Any osType;
|
OsType.Any osType;
|
||||||
|
@ -25,17 +26,11 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataStoreState mergeCopy(DataStoreState newer) {
|
public void merge(DataStoreState newer) {
|
||||||
var shellStoreState = (ShellStoreState) newer;
|
var shellStoreState = (ShellStoreState) newer;
|
||||||
var b = toBuilder();
|
osType = useNewer(osType, shellStoreState.getOsType());
|
||||||
mergeBuilder(shellStoreState, b);
|
osName = useNewer(osName, shellStoreState.getOsName());
|
||||||
return b.build();
|
shellDialect = useNewer(shellDialect, shellStoreState.getShellDialect());
|
||||||
}
|
running = useNewer(running, shellStoreState.getRunning());
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,49 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder
|
||||||
public abstract class DataStoreState {
|
public abstract class DataStoreState {
|
||||||
|
|
||||||
public DataStoreState() {}
|
public DataStoreState() {}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <DS extends DataStoreState> DS asNeeded() {
|
|
||||||
return (DS) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static <T> T useNewer(T older, T newer) {
|
protected static <T> T useNewer(T older, T newer) {
|
||||||
return newer != null ? newer : older;
|
return newer != null ? newer : older;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataStoreState mergeCopy(DataStoreState newer) {
|
public abstract void merge(DataStoreState newer);
|
||||||
return this;
|
|
||||||
|
@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;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
import io.xpipe.core.util.DataStateProvider;
|
import io.xpipe.core.util.DataStateProvider;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
|
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));
|
return getStateClass().cast(m.invoke(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
default T getState() {
|
default T getState() {
|
||||||
return DataStateProvider.get().getState(this, this::createDefaultState);
|
return (T)
|
||||||
|
DataStateProvider.get().getState(this, this::createDefaultState).deepCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
default void setState(T val) {
|
default void setState(T val) {
|
||||||
DataStateProvider.get().setState(this, val);
|
DataStateProvider.get().setState(this, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default T getState(Supplier<T> def) {
|
||||||
|
return DataStateProvider.get().getState(this, def);
|
||||||
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
default Class<T> getStateClass() {
|
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.
|
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
|
## Git vault improvements
|
||||||
|
|
||||||
The conflict resolution has been improved
|
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
|
- 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
|
- 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
|
## Other
|
||||||
|
|
||||||
- You can now add simple RDP connections without a file
|
- 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
|
- 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
|
- 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 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 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()) {
|
if (org.gradle.internal.os.OperatingSystem.current().isLinux()) {
|
||||||
options += ['--strip-native-debug-symbols']
|
options.addAll('--strip-native-debug-symbols', 'exclude-debuginfo-files')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useBundledJavaFx) {
|
if (useBundledJavaFx) {
|
||||||
|
|
|
@ -31,13 +31,15 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
|
||||||
|
|
||||||
var def = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
var def = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
||||||
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
"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);
|
s.setState(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
var bring = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
var bring = StoreToggleComp.<ScriptGroupStore>simpleToggle(
|
||||||
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
"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);
|
s.setState(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,15 @@ import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.ShellTemp;
|
import io.xpipe.app.util.ShellTemp;
|
||||||
import io.xpipe.app.util.Validators;
|
import io.xpipe.app.util.Validators;
|
||||||
import io.xpipe.core.process.ShellControl;
|
|
||||||
import io.xpipe.core.process.ShellInitCommand;
|
import io.xpipe.core.process.ShellInitCommand;
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.DataStoreState;
|
import io.xpipe.core.store.DataStoreState;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.StatefulDataStore;
|
import io.xpipe.core.store.StatefulDataStore;
|
||||||
import io.xpipe.core.util.JacksonizedValue;
|
import io.xpipe.core.util.JacksonizedValue;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
@ -221,12 +222,20 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
|
|
||||||
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
|
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
|
||||||
|
|
||||||
@Value
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
@EqualsAndHashCode(callSuper=true)
|
@Setter
|
||||||
@SuperBuilder(toBuilder = true)
|
@Getter
|
||||||
|
@SuperBuilder
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
public static class State extends DataStoreState {
|
public static class State extends DataStoreState {
|
||||||
boolean isDefault;
|
boolean isDefault;
|
||||||
boolean bringToShell;
|
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(
|
var def = StoreToggleComp.<SimpleScriptStore>simpleToggle(
|
||||||
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
"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);
|
s.setState(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
var bring = StoreToggleComp.<SimpleScriptStore>simpleToggle(
|
var bring = StoreToggleComp.<SimpleScriptStore>simpleToggle(
|
||||||
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
"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);
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -457,6 +457,3 @@ history=Browsing-historik
|
||||||
skipAll=Spring alle over
|
skipAll=Spring alle over
|
||||||
notes=Bemærkninger
|
notes=Bemærkninger
|
||||||
addNotes=Tilføj noter
|
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
|
skipAll=Alles überspringen
|
||||||
notes=Anmerkungen
|
notes=Anmerkungen
|
||||||
addNotes=Notizen hinzufügen
|
addNotes=Notizen hinzufügen
|
||||||
order=Bestellen ...
|
|
||||||
stickToTop=Oben bleiben
|
|
||||||
orderAheadOf=Vorbestellen ...
|
|
||||||
|
|
|
@ -454,7 +454,3 @@ history=Browsing history
|
||||||
skipAll=Skip all
|
skipAll=Skip all
|
||||||
notes=Notes
|
notes=Notes
|
||||||
addNotes=Add 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
|
skipAll=Saltar todo
|
||||||
notes=Notas
|
notes=Notas
|
||||||
addNotes=Añadir 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
|
skipAll=Sauter tout
|
||||||
notes=Notes
|
notes=Notes
|
||||||
addNotes=Ajouter des 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
|
skipAll=Salta tutto
|
||||||
notes=Note
|
notes=Note
|
||||||
addNotes=Aggiungi note
|
addNotes=Aggiungi note
|
||||||
order=Ordinare ...
|
|
||||||
stickToTop=Continua a essere in cima
|
|
||||||
orderAheadOf=Ordina prima di ...
|
|
||||||
|
|
|
@ -438,6 +438,3 @@ history=閲覧履歴
|
||||||
skipAll=すべてスキップする
|
skipAll=すべてスキップする
|
||||||
notes=備考
|
notes=備考
|
||||||
addNotes=メモを追加する
|
addNotes=メモを追加する
|
||||||
order=注文する
|
|
||||||
stickToTop=トップをキープする
|
|
||||||
orderAheadOf=先に注文する
|
|
||||||
|
|
|
@ -438,6 +438,3 @@ history=Browsegeschiedenis
|
||||||
skipAll=Alles overslaan
|
skipAll=Alles overslaan
|
||||||
notes=Opmerkingen
|
notes=Opmerkingen
|
||||||
addNotes=Opmerkingen toevoegen
|
addNotes=Opmerkingen toevoegen
|
||||||
order=Bestellen ...
|
|
||||||
stickToTop=Bovenaan houden
|
|
||||||
orderAheadOf=Vooruitbestellen ...
|
|
||||||
|
|
|
@ -438,6 +438,3 @@ history=Histórico de navegação
|
||||||
skipAll=Salta tudo
|
skipAll=Salta tudo
|
||||||
notes=Nota
|
notes=Nota
|
||||||
addNotes=Adiciona notas
|
addNotes=Adiciona notas
|
||||||
order=Encomenda ...
|
|
||||||
stickToTop=Mantém-te no topo
|
|
||||||
orderAheadOf=Encomenda antes de ...
|
|
||||||
|
|
|
@ -438,6 +438,3 @@ history=История просмотров
|
||||||
skipAll=Пропустить все
|
skipAll=Пропустить все
|
||||||
notes=Заметки
|
notes=Заметки
|
||||||
addNotes=Добавляй заметки
|
addNotes=Добавляй заметки
|
||||||
order=Заказать ...
|
|
||||||
stickToTop=Держись на высоте
|
|
||||||
orderAheadOf=Заказать заранее ...
|
|
||||||
|
|
|
@ -439,6 +439,3 @@ history=Tarama geçmişi
|
||||||
skipAll=Tümünü atla
|
skipAll=Tümünü atla
|
||||||
notes=Notlar
|
notes=Notlar
|
||||||
addNotes=Notlar ekleyin
|
addNotes=Notlar ekleyin
|
||||||
order=Sipariş ...
|
|
||||||
stickToTop=Zirvede kal
|
|
||||||
orderAheadOf=Önceden sipariş verin ...
|
|
||||||
|
|
|
@ -438,6 +438,3 @@ history=浏览历史
|
||||||
skipAll=全部跳过
|
skipAll=全部跳过
|
||||||
notes=说明
|
notes=说明
|
||||||
addNotes=添加注释
|
addNotes=添加注释
|
||||||
order=订购 ...
|
|
||||||
stickToTop=保持在顶部
|
|
||||||
orderAheadOf=提前订购...
|
|
||||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
||||||
9.4-3
|
9.4
|
||||||
|
|
Loading…
Reference in a new issue