Reformat and cleanup

This commit is contained in:
crschnick 2024-02-21 07:35:28 +00:00
parent 310647a6d8
commit 68d3c4263b
299 changed files with 5222 additions and 4381 deletions

View file

@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
public class StartupTest {
@Test
public void test( ) throws Exception {
public void test() throws Exception {
BeaconDaemonController.start(XPipeDaemonMode.TRAY);
BeaconDaemonController.stop();
}

View file

@ -1,4 +1,4 @@
open module io.xpipe.app.localTest {
requires org.junit.jupiter.api;
requires io.xpipe.app;
}
}

View file

@ -9,6 +9,5 @@ public class Test extends LocalExtensionTest {
public void test() {
System.out.println("a");
System.out.println(DataStorage.get().getStoreEntries());
}
}

View file

@ -14,10 +14,10 @@ public class Main {
// Since this is not marked as a console application, it will not print anything when you run it in a console
// So sadly there can't be a help command
// if (args.length == 1 && args[0].equals("--help")) {
// System.out.println("HELP");
// return;
// }
// if (args.length == 1 && args[0].equals("--help")) {
// System.out.println("HELP");
// return;
// }
OperationMode.init(args);
}

View file

@ -14,14 +14,6 @@ import java.util.stream.Collectors;
public class BrowserAlerts {
public enum FileConflictChoice {
CANCEL,
SKIP,
SKIP_ALL,
REPLACE,
REPLACE_ALL
}
public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) {
var map = new LinkedHashMap<ButtonType, FileConflictChoice>();
map.put(new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL);
@ -36,10 +28,14 @@ public class BrowserAlerts {
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("fileConflictAlertTitle"));
alert.setHeaderText(AppI18n.get("fileConflictAlertHeader"));
AppWindowHelper.setContent(alert, AppI18n.get(multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file));
AppWindowHelper.setContent(
alert,
AppI18n.get(
multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
map.sequencedKeySet().forEach(buttonType -> alert.getButtonTypes().add(buttonType));
map.sequencedKeySet()
.forEach(buttonType -> alert.getButtonTypes().add(buttonType));
})
.map(map::get)
.orElse(FileConflictChoice.CANCEL);
@ -86,4 +82,12 @@ public class BrowserAlerts {
}
return names;
}
public enum FileConflictChoice {
CANCEL,
SKIP,
SKIP_ALL,
REPLACE,
REPLACE_ALL
}
}

View file

@ -49,20 +49,32 @@ final class BrowserBookmarkComp extends SimpleComp {
var filterText = new SimpleStringProperty();
var open = PlatformThread.sync(model.getSelected());
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore ||
storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore) && storeEntryWrapper.getEntry().getValidity().isUsable();
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
};
var selectedCategory = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
var selectedCategory = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue());
var section = StoreSectionMiniComp.createList(
StoreSection.createTopLevel(StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory), (s, comp) -> {
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory),
(s, comp) -> {
BooleanProperty busy = new SimpleBooleanProperty(false);
comp.disable(Bindings.createBooleanBinding(() -> {
return busy.get() || !applicable.test(s.getWrapper());
}, busy));
comp.disable(Bindings.createBooleanBinding(
() -> {
return busy.get() || !applicable.test(s.getWrapper());
},
busy));
comp.apply(struc -> {
open.addListener((observable, oldValue, newValue) -> {
struc.get().pseudoClassStateChanged(SELECTED,
newValue != null && newValue.getEntry().get().equals(s.getWrapper().getEntry()));
struc.get()
.pseudoClassStateChanged(
SELECTED,
newValue != null
&& newValue.getEntry()
.get()
.equals(s.getWrapper()
.getEntry()));
});
struc.get().setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
@ -83,14 +95,21 @@ final class BrowserBookmarkComp extends SimpleComp {
});
});
});
var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(),
selectedCategory).styleClass(Styles.LEFT_PILL);
var filter = new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
var category = new DataStoreCategoryChoiceComp(
StoreViewState.get().getAllConnectionsCategory(),
StoreViewState.get().getActiveCategory(),
selectedCategory)
.styleClass(Styles.LEFT_PILL);
var filter =
new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
var top = new HorizontalComp(List.of(category.minWidth(Region.USE_PREF_SIZE), filter.hgrow())).styleClass("categories").apply(struc -> {
AppFont.medium(struc.get());
struc.get().setFillHeight(true);
}).createRegion();
var top = new HorizontalComp(List.of(category.minWidth(Region.USE_PREF_SIZE), filter.hgrow()))
.styleClass("categories")
.apply(struc -> {
AppFont.medium(struc.get());
struc.get().setFillHeight(true);
})
.createRegion();
var r = section.vgrow().createRegion();
var content = new VBox(top, r);
content.setFillWidth(true);
@ -108,8 +127,7 @@ final class BrowserBookmarkComp extends SimpleComp {
activeTask = new TimerTask() {
@Override
public void run() {
if (activeTask != this) {
}
if (activeTask != this) {}
// Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded()));
}

View file

@ -24,18 +24,6 @@ import java.util.stream.Collectors;
public class BrowserClipboard {
@Value
public static class Instance {
UUID uuid;
FileSystem.FileEntry baseDirectory;
List<FileSystem.FileEntry> entries;
public String toClipboardString() {
return entries.stream().map(fileEntry -> "\"" + fileEntry.getPath() + "\"").collect(
Collectors.joining(ProcessControlProvider.get().getEffectiveLocalDialect().getNewLine().getNewLineString()));
}
}
public static final Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
public static Instance currentDragClipboard;
@ -53,7 +41,8 @@ public class BrowserClipboard {
}
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
var files = data.stream().map(string -> string.toPath()).toList();
var files =
data.stream().map(string -> string.toPath()).toList();
if (files.size() == 0) {
return;
}
@ -121,4 +110,20 @@ public class BrowserClipboard {
return null;
}
@Value
public static class Instance {
UUID uuid;
FileSystem.FileEntry baseDirectory;
List<FileSystem.FileEntry> entries;
public String toClipboardString() {
return entries.stream()
.map(fileEntry -> "\"" + fileEntry.getPath() + "\"")
.collect(Collectors.joining(ProcessControlProvider.get()
.getEffectiveLocalDialect()
.getNewLine()
.getNewLineString()));
}
}
}

View file

@ -59,28 +59,32 @@ public class BrowserComp extends SimpleComp {
});
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
return false;
}, model.getOpenFileSystems(), model.getSelected())));
return false;
},
model.getOpenFileSystems(),
model.getSelected())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var splitPane = new SideSplitPaneComp(vertical, createTabs())
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
// AppFont.small(r);
@ -99,12 +103,16 @@ public class BrowserComp extends SimpleComp {
selected.setSpacing(10);
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren().setAll(c.getList().stream().map(s -> {
var field = new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
}).toList());
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
})
.toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
@ -123,12 +131,16 @@ public class BrowserComp extends SimpleComp {
}
private Comp<?> createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(Comp.of(() -> createTabPane()),
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(() -> {
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
}, model.getOpenFileSystems())));
Bindings.createBooleanBinding(
() -> {
return model.getOpenFileSystems().size() == 0
&& !model.getMode().isChooser();
},
model.getOpenFileSystems())));
return multi;
}
@ -149,7 +161,8 @@ public class BrowserComp extends SimpleComp {
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
tabs.getSelectionModel()
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
var modifying = new SimpleBooleanProperty();
@ -165,9 +178,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(newValue))
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
@ -182,9 +195,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var toSelect = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
var toSelect = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getKey().equals(newValue))
.findAny()
.map(Map.Entry::getValue)
.orElse(null);
@ -223,9 +236,9 @@ public class BrowserComp extends SimpleComp {
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
while (c.next()) {
for (var r : c.getRemoved()) {
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(r))
.findAny()
.orElse(null);
@ -248,14 +261,22 @@ public class BrowserComp extends SimpleComp {
ring.setMinSize(16, 16);
ring.setPrefSize(16, 16);
ring.setMaxSize(16, 16);
ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
ring.progressProperty()
.bind(Bindings.createDoubleBinding(
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
var image = model.getEntry()
.get()
.getProvider()
.getDisplayIconFileName(model.getEntry().getStore());
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
return model.getBusy().get() ? ring : logo;
}, PlatformThread.sync(model.getBusy())));
tab.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
return model.getBusy().get() ? ring : logo;
},
PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(new OpenFileSystemComp(model).createSimple());
@ -276,12 +297,17 @@ public class BrowserComp extends SimpleComp {
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
c.getStyleClass().add("color-box");
var color = DataStorage.get().getRootForEntry(model.getEntry().get()).getColor();
var color = DataStorage.get()
.getRootForEntry(model.getEntry().get())
.getColor();
if (color != null) {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(
() -> tabs.getSelectionModel().select(tab)));
});
}
});

View file

@ -24,6 +24,17 @@ final class BrowserContextMenu extends ContextMenu {
createMenu();
}
private static List<BrowserEntry> resolveIfNeeded(BrowserAction action, List<BrowserEntry> selected) {
return action.automaticallyResolveLinks()
? selected.stream()
.map(browserEntry -> new BrowserEntry(
browserEntry.getRawFileEntry().resolved(),
browserEntry.getModel(),
browserEntry.isSynthetic()))
.toList()
: selected;
}
private void createMenu() {
AppFont.normal(this.getStyleableNode());
@ -81,7 +92,10 @@ final class BrowserContextMenu extends ContextMenu {
}
m.setDisable(!a.isActive(model, used));
if (la.getProFeatureId() != null && !LicenseProvider.get().getFeature(la.getProFeatureId()).isSupported()) {
if (la.getProFeatureId() != null
&& !LicenseProvider.get()
.getFeature(la.getProFeatureId())
.isSupported()) {
m.setDisable(true);
m.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
}
@ -91,15 +105,4 @@ final class BrowserContextMenu extends ContextMenu {
}
}
}
private static List<BrowserEntry> resolveIfNeeded(BrowserAction action, List<BrowserEntry> selected) {
return action.automaticallyResolveLinks()
? selected.stream()
.map(browserEntry -> new BrowserEntry(
browserEntry.getRawFileEntry().resolved(),
browserEntry.getModel(),
browserEntry.isSynthetic()))
.toList()
: selected;
}
}

View file

@ -81,18 +81,18 @@ final class BrowserFileListComp extends SimpleComp {
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
var sizeCol = new TableColumn<BrowserEntry, Number>("Size");
sizeCol.setCellValueFactory(param ->
new SimpleLongProperty(param.getValue().getRawFileEntry().resolved().getSize()));
sizeCol.setCellValueFactory(param -> new SimpleLongProperty(
param.getValue().getRawFileEntry().resolved().getSize()));
sizeCol.setCellFactory(col -> new FileSizeCell());
var mtimeCol = new TableColumn<BrowserEntry, Instant>("Modified");
mtimeCol.setCellValueFactory(param ->
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getDate()));
mtimeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
param.getValue().getRawFileEntry().resolved().getDate()));
mtimeCol.setCellFactory(col -> new FileTimeCell());
var modeCol = new TableColumn<BrowserEntry, String>("Attributes");
modeCol.setCellValueFactory(param ->
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getMode()));
modeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
param.getValue().getRawFileEntry().resolved().getMode()));
modeCol.setCellFactory(col -> new FileModeCell());
modeCol.setSortable(false);
@ -247,12 +247,20 @@ final class BrowserFileListComp extends SimpleComp {
}
if (row.getItem() != null
&& row.getItem().getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
&& row.getItem()
.getRawFileEntry()
.resolved()
.getKind()
== FileKind.DIRECTORY) {
return event.getButton() == MouseButton.SECONDARY;
}
if (row.getItem() != null
&& row.getItem().getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) {
&& row.getItem()
.getRawFileEntry()
.resolved()
.getKind()
!= FileKind.DIRECTORY) {
return event.getButton() == MouseButton.SECONDARY
|| event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2;
}
@ -424,6 +432,54 @@ final class BrowserFileListComp extends SimpleComp {
}
}
private static class FileSizeCell extends TableCell<BrowserEntry, Number> {
@Override
protected void updateItem(Number fileSize, boolean empty) {
super.updateItem(fileSize, empty);
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
} else {
var path = getTableRow().getItem();
if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
setText("");
} else {
setText(byteCount(fileSize.longValue()));
}
}
}
}
private static class FileModeCell extends TableCell<BrowserEntry, String> {
@Override
protected void updateItem(String mode, boolean empty) {
super.updateItem(mode, empty);
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
} else {
setText(mode);
}
}
}
private static class FileTimeCell extends TableCell<BrowserEntry, Instant> {
@Override
protected void updateItem(Instant fileTime, boolean empty) {
super.updateItem(fileTime, empty);
if (empty) {
setText(null);
} else {
setText(
fileTime != null
? HumanReadableFormat.date(
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
: "");
}
}
}
private class FilenameCell extends TableCell<BrowserEntry, String> {
private final StringProperty img = new SimpleStringProperty();
@ -518,52 +574,4 @@ final class BrowserFileListComp extends SimpleComp {
}
}
}
private static class FileSizeCell extends TableCell<BrowserEntry, Number> {
@Override
protected void updateItem(Number fileSize, boolean empty) {
super.updateItem(fileSize, empty);
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
} else {
var path = getTableRow().getItem();
if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
setText("");
} else {
setText(byteCount(fileSize.longValue()));
}
}
}
}
private static class FileModeCell extends TableCell<BrowserEntry, String> {
@Override
protected void updateItem(String mode, boolean empty) {
super.updateItem(mode, empty);
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
} else {
setText(mode);
}
}
}
private static class FileTimeCell extends TableCell<BrowserEntry, Instant> {
@Override
protected void updateItem(Instant fileTime, boolean empty) {
super.updateItem(fileTime, empty);
if (empty) {
setText(null);
} else {
setText(
fileTime != null
? HumanReadableFormat.date(
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
: "");
}
}
}
}

View file

@ -27,7 +27,8 @@ public class BrowserFileListCompEntry {
private Point2D lastOver = new Point2D(-1, -1);
private TimerTask activeTask;
public BrowserFileListCompEntry(TableView<BrowserEntry> tv, Node row, BrowserEntry item, BrowserFileListModel model) {
public BrowserFileListCompEntry(
TableView<BrowserEntry> tv, Node row, BrowserEntry item, BrowserFileListModel model) {
this.tv = tv;
this.row = row;
this.item = item;
@ -59,14 +60,18 @@ public class BrowserFileListCompEntry {
var all = tv.getItems();
var index = item != null ? all.indexOf(item) : all.size() - 1;
var min = Math.min(index, tv.getSelectionModel().getSelectedIndices().stream()
.mapToInt(value -> value)
.min()
.orElse(1));
var max = Math.max(index, tv.getSelectionModel().getSelectedIndices().stream()
.mapToInt(value -> value)
.max()
.orElse(all.indexOf(item)));
var min = Math.min(
index,
tv.getSelectionModel().getSelectedIndices().stream()
.mapToInt(value -> value)
.min()
.orElse(1));
var max = Math.max(
index,
tv.getSelectionModel().getSelectedIndices().stream()
.mapToInt(value -> value)
.max()
.orElse(all.indexOf(item)));
var toSelect = new ArrayList<BrowserEntry>();
for (int i = min; i <= max; i++) {
@ -98,13 +103,15 @@ public class BrowserFileListCompEntry {
return false;
}
if (!Objects.equals(model.getFileSystemModel().getFileSystem(), cb.getEntries().getFirst().getFileSystem())) {
if (!Objects.equals(
model.getFileSystemModel().getFileSystem(),
cb.getEntries().getFirst().getFileSystem())) {
return true;
}
// Prevent drag and drops of files into the current directory
if (cb.getBaseDirectory() != null && cb
.getBaseDirectory()
if (cb.getBaseDirectory() != null
&& cb.getBaseDirectory()
.getPath()
.equals(model.getFileSystemModel().getCurrentDirectory().getPath())
&& (item == null || item.getRawFileEntry().getKind() != FileKind.DIRECTORY)) {

View file

@ -2,8 +2,8 @@ package io.xpipe.app.browser;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
@ -99,8 +99,9 @@ public final class BrowserFileListModel {
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
var comp = comparatorProperty.getValue();
Comparator<? super BrowserEntry> us =
comp != null ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) : syntheticFirst.thenComparing(dirsFirst);
Comparator<? super BrowserEntry> us = comp != null
? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp)
: syntheticFirst.thenComparing(dirsFirst);
l.sort(us);
}
@ -110,14 +111,17 @@ public final class BrowserFileListModel {
boolean exists;
try {
exists = fileSystemModel.getFileSystem().fileExists(newFullPath) || fileSystemModel.getFileSystem().directoryExists(newFullPath);
exists = fileSystemModel.getFileSystem().fileExists(newFullPath)
|| fileSystemModel.getFileSystem().directoryExists(newFullPath);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
if (exists) {
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist").expected().handle();
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist")
.expected()
.handle();
fileSystemModel.refresh();
return false;
}

View file

@ -16,6 +16,14 @@ import org.kordamp.ikonli.javafx.FontIcon;
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
private final OpenFileSystemModel model;
private final Property<String> filterString;
public BrowserFilterComp(OpenFileSystemModel model, Property<String> filterString) {
this.model = model;
this.filterString = filterString;
}
@Override
public Structure createBase() {
var expanded = new SimpleBooleanProperty();
@ -98,12 +106,4 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
return box;
}
}
private final OpenFileSystemModel model;
private final Property<String> filterString;
public BrowserFilterComp(OpenFileSystemModel model, Property<String> filterString) {
this.model = model;
this.filterString = filterString;
}
}

View file

@ -33,6 +33,7 @@ public class BrowserModel {
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final BrowserSavedState savedState;
@Setter
private Consumer<List<FileReference>> onFinish;
@ -101,8 +102,10 @@ public class BrowserModel {
return;
}
var stores = chosen.stream().map(
entry -> new FileReference(selected.getValue().getEntry(), entry.getRawFileEntry().getPath())).toList();
var stores = chosen.stream()
.map(entry -> new FileReference(
selected.getValue().getEntry(), entry.getRawFileEntry().getPath()))
.toList();
onFinish.accept(stores);
}
@ -113,8 +116,11 @@ public class BrowserModel {
}
private void closeFileSystemSync(OpenFileSystemModel open) {
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) && savedState != null && open.getCurrentPath().get() != null) {
savedState.add(new BrowserSavedState.Entry(open.getEntry().get().getUuid(), open.getCurrentPath().get()));
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get())
&& savedState != null
&& open.getCurrentPath().get() != null) {
savedState.add(new BrowserSavedState.Entry(
open.getEntry().get().getUuid(), open.getCurrentPath().get()));
}
open.closeSync();
synchronized (BrowserModel.this) {
@ -122,7 +128,10 @@ public class BrowserModel {
}
}
public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, FailableFunction<OpenFileSystemModel, String, Exception> path, BooleanProperty externalBusy) {
public void openFileSystemAsync(
DataStoreEntryRef<? extends FileSystemStore> store,
FailableFunction<OpenFileSystemModel, String, Exception> path,
BooleanProperty externalBusy) {
if (store == null) {
return;
}

View file

@ -106,7 +106,6 @@ public class BrowserNavBar extends SimpleComp {
})
.augment(new SimpleCompStructure<>(homeButton));
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
historyButton.setAccessibleText("History");
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
@ -146,7 +145,6 @@ public class BrowserNavBar extends SimpleComp {
.maxHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
((Region) struc.get().getChildren().get(2))
.minHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
@ -197,7 +195,8 @@ public class BrowserNavBar extends SimpleComp {
cm.getItems().add(current);
}
var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream().toList();
var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream()
.toList();
if (!b.isEmpty()) {
cm.getItems().add(new SeparatorMenuItem());
}

View file

@ -20,12 +20,6 @@ import java.util.List;
@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class)
public class BrowserSavedStateImpl implements BrowserSavedState {
static BrowserSavedStateImpl load() {
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
});
}
@JsonSerialize(as = List.class)
ObservableList<Entry> lastSystems;
@ -33,25 +27,10 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
this.lastSystems = FXCollections.observableArrayList(lastSystems);
}
public static class Deserializer extends StdDeserializer<BrowserSavedStateImpl> {
protected Deserializer() {
super(BrowserSavedStateImpl.class);
}
@Override
@SneakyThrows
public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
JavaType javaType = JacksonMapper.getDefault()
.getTypeFactory()
.constructCollectionLikeType(List.class, Entry.class);
List<Entry> ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType);
if (ls == null) {
ls = List.of();
}
return new BrowserSavedStateImpl(ls);
}
static BrowserSavedStateImpl load() {
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
});
}
@Override
@ -72,4 +51,24 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
public ObservableList<Entry> getEntries() {
return lastSystems;
}
public static class Deserializer extends StdDeserializer<BrowserSavedStateImpl> {
protected Deserializer() {
super(BrowserSavedStateImpl.class);
}
@Override
@SneakyThrows
public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
JavaType javaType =
JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(List.class, Entry.class);
List<Entry> ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType);
if (ls == null) {
ls = List.of();
}
return new BrowserSavedStateImpl(ls);
}
}
}

View file

@ -31,6 +31,12 @@ import java.util.function.Function;
@AllArgsConstructor
public class BrowserSelectionListComp extends SimpleComp {
ObservableList<FileSystem.FileEntry> list;
Function<FileSystem.FileEntry, ObservableValue<String>> nameTransformation;
public BrowserSelectionListComp(ObservableList<FileSystem.FileEntry> list) {
this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath())));
}
public static Image snapshot(ObservableList<FileSystem.FileEntry> list) {
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
var scene = new Scene(r);
@ -41,13 +47,6 @@ public class BrowserSelectionListComp extends SimpleComp {
return r.snapshot(parameters, null);
}
ObservableList<FileSystem.FileEntry> list;
Function<FileSystem.FileEntry, ObservableValue<String>> nameTransformation;
public BrowserSelectionListComp(ObservableList<FileSystem.FileEntry> list) {
this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath())));
}
@Override
protected Region createSimple() {
var c = new ListBoxViewComp<>(list, list, entry -> {

View file

@ -24,7 +24,12 @@ public class BrowserStatusBarComp extends SimpleComp {
@Override
protected Region createSimple() {
var bar = new ToolBar();
bar.getItems().setAll(createClipboardStatus().createRegion(), createProgressStatus().createRegion(), new Spacer(), createSelectionStatus().createRegion());
bar.getItems()
.setAll(
createClipboardStatus().createRegion(),
createProgressStatus().createRegion(),
new Spacer(),
createSelectionStatus().createRegion());
bar.getStyleClass().add("status-bar");
bar.setOnDragDetected(event -> {
event.consume();
@ -40,20 +45,26 @@ public class BrowserStatusBarComp extends SimpleComp {
private Comp<?> createProgressStatus() {
var transferredCount = PlatformThread.sync(Bindings.createStringBinding(
() -> {
return HumanReadableFormat.byteCount(model.getProgress().getValue().getTransferred());
return HumanReadableFormat.byteCount(
model.getProgress().getValue().getTransferred());
},
model.getProgress()));
var allCount = PlatformThread.sync(Bindings.createStringBinding(
() -> {
return HumanReadableFormat.byteCount(model.getProgress().getValue().getTotal());
return HumanReadableFormat.byteCount(
model.getProgress().getValue().getTotal());
},
model.getProgress()));
var progressComp = new LabelComp(Bindings.createStringBinding(
() -> {
if (model.getProgress().getValue() == null || model.getProgress().getValue().done()) {
if (model.getProgress().getValue() == null
|| model.getProgress().getValue().done()) {
return null;
} else {
return (model.getProgress().getValue().getName() != null ? model.getProgress().getValue().getName() + " " : "") + transferredCount.getValue() + " / " + allCount.getValue();
return (model.getProgress().getValue().getName() != null
? model.getProgress().getValue().getName() + " "
: "")
+ transferredCount.getValue() + " / " + allCount.getValue();
}
},
transferredCount,

View file

@ -44,20 +44,33 @@ public class BrowserTransferComp extends SimpleComp {
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
var list = new BrowserSelectionListComp(binding, entry -> Bindings.createStringBinding(() -> {
var sourceItem = model.getItems().stream().filter(item -> item.getFileEntry() == entry).findAny();
if (sourceItem.isEmpty()) {
return "?";
}
var name = sourceItem.get().downloadFinished().get() ? "Local" : DataStorage.get().getStoreDisplayName(entry.getFileSystem().getStore()).orElse("?");
return FileNames.getFileName(entry.getPath()) + " (" + name + ")";
}, model.getAllDownloaded()))
var list = new BrowserSelectionListComp(
binding,
entry -> Bindings.createStringBinding(
() -> {
var sourceItem = model.getItems().stream()
.filter(item -> item.getFileEntry() == entry)
.findAny();
if (sourceItem.isEmpty()) {
return "?";
}
var name =
sourceItem.get().downloadFinished().get()
? "Local"
: DataStorage.get()
.getStoreDisplayName(entry.getFileSystem()
.getStore())
.orElse("?");
return FileNames.getFileName(entry.getPath()) + " (" + name + ")";
},
model.getAllDownloaded()))
.apply(struc -> struc.get().setMinHeight(150))
.grow(false, true);
var dragNotice = new LabelComp(model.getAllDownloaded().flatMap(aBoolean -> aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
var dragNotice = new LabelComp(model.getAllDownloaded()
.flatMap(aBoolean ->
aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export")))
.hide(PlatformThread.sync(
BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
.hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
.grow(true, false)
.apply(struc -> struc.get().setPadding(new Insets(8)));
@ -95,7 +108,8 @@ public class BrowserTransferComp extends SimpleComp {
}
// Accept drops from outside the app window
if (event.getGestureSource() == null && !event.getDragboard().getFiles().isEmpty()) {
if (event.getGestureSource() == null
&& !event.getDragboard().getFiles().isEmpty()) {
event.acceptTransferModes(TransferMode.ANY);
event.consume();
}
@ -109,7 +123,11 @@ public class BrowserTransferComp extends SimpleComp {
}
var files = drag.getEntries();
model.drop(model.getBrowserModel().getSelected().getValue(), files);
model.drop(
model.getBrowserModel()
.getSelected()
.getValue(),
files);
event.setDropCompleted(true);
event.consume();
}
@ -126,7 +144,9 @@ public class BrowserTransferComp extends SimpleComp {
return;
}
var selected = model.getItems().stream().map(BrowserTransferModel.Item::getFileEntry).toList();
var selected = model.getItems().stream()
.map(BrowserTransferModel.Item::getFileEntry)
.toList();
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
var cc = BrowserClipboard.startDrag(null, selected);
@ -143,9 +163,8 @@ public class BrowserTransferComp extends SimpleComp {
return Optional.<File>empty();
}
return Optional.of(file
.toRealPath()
.toFile());
return Optional.of(
file.toRealPath().toFile());
} catch (IOException e) {
throw new RuntimeException(e);
}

View file

@ -36,30 +36,6 @@ public class BrowserTransferModel {
t.setName("file downloader");
return t;
});
@Value
public static class Item {
OpenFileSystemModel openFileSystemModel;
String name;
FileSystem.FileEntry fileEntry;
Path localFile;
Property<BrowserTransferProgress> progress;
public Item(OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) {
this.openFileSystemModel = openFileSystemModel;
this.name = name;
this.fileEntry = fileEntry;
this.localFile = localFile;
this.progress = new SimpleObjectProperty<>(BrowserTransferProgress.empty(fileEntry.getName(), fileEntry.getSize()));
}
public ObservableBooleanValue downloadFinished() {
return Bindings.createBooleanBinding(() -> {
return progress.getValue().done();
}, progress);
}
}
BrowserModel browserModel;
ObservableList<Item> items = FXCollections.observableArrayList();
BooleanProperty downloading = new SimpleBooleanProperty();
@ -132,17 +108,15 @@ public class BrowserTransferModel {
continue;
}
if (item.getOpenFileSystemModel() != null && item.getOpenFileSystemModel().isClosed()) {
if (item.getOpenFileSystemModel() != null
&& item.getOpenFileSystemModel().isClosed()) {
continue;
}
try {
try (var b = new BooleanScope(downloading).start()) {
FileSystemHelper.dropFilesInto(
FileSystemHelper.getLocal(TEMP),
List.of(item.getFileEntry()),
true,
progress -> {
FileSystemHelper.getLocal(TEMP), List.of(item.getFileEntry()), true, progress -> {
item.getProgress().setValue(progress);
item.getOpenFileSystemModel().getProgress().setValue(progress);
});
@ -155,4 +129,31 @@ public class BrowserTransferModel {
allDownloaded.set(true);
});
}
@Value
public static class Item {
OpenFileSystemModel openFileSystemModel;
String name;
FileSystem.FileEntry fileEntry;
Path localFile;
Property<BrowserTransferProgress> progress;
public Item(
OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) {
this.openFileSystemModel = openFileSystemModel;
this.name = name;
this.fileEntry = fileEntry;
this.localFile = localFile;
this.progress =
new SimpleObjectProperty<>(BrowserTransferProgress.empty(fileEntry.getName(), fileEntry.getSize()));
}
public ObservableBooleanValue downloadFinished() {
return Bindings.createBooleanBinding(
() -> {
return progress.getValue().done();
},
progress);
}
}
}

View file

@ -5,6 +5,10 @@ import lombok.Value;
@Value
public class BrowserTransferProgress {
String name;
long transferred;
long total;
static BrowserTransferProgress empty() {
return new BrowserTransferProgress(null, 0, 0);
}
@ -17,10 +21,6 @@ public class BrowserTransferProgress {
return new BrowserTransferProgress(name, size, size);
}
String name;
long transferred;
long total;
public boolean done() {
return transferred >= total;
}

View file

@ -42,7 +42,9 @@ public class BrowserWelcomeComp extends SimpleComp {
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
vbox.setAlignment(Pos.CENTER_LEFT);
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75).padding(new Insets(5, 0, 0, 0)).createRegion();
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75)
.padding(new Insets(5, 0, 0, 0))
.createRegion();
var hbox = new HBox(img, vbox);
hbox.setAlignment(Pos.CENTER_LEFT);
hbox.setSpacing(15);
@ -68,10 +70,14 @@ public class BrowserWelcomeComp extends SimpleComp {
});
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
var header = new LabelComp(Bindings.createStringBinding(() -> {
return !empty.get() ? "You were recently connected to the following systems:" :
"Here you will be able to see where you left off last time.";
}, empty)).createRegion();
var header = new LabelComp(Bindings.createStringBinding(
() -> {
return !empty.get()
? "You were recently connected to the following systems:"
: "Here you will be able to see where you left off last time.";
},
empty))
.createRegion();
AppFont.setSize(header, 1);
vbox.getChildren().add(header);
@ -79,22 +85,32 @@ public class BrowserWelcomeComp extends SimpleComp {
storeList.setSpacing(8);
var listBox = new ListBoxViewComp<>(list, list, e -> {
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40);
view.padding(new Insets(2, 8, 2, 8));
var content =
JfxHelper.createNamedEntry(DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
var disable = new SimpleBooleanProperty();
return new ButtonComp(null, content, () -> {
ThreadHelper.runAsync(() -> {
model.restoreStateAsync(e, disable);
});
}).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-listBox").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
}).apply(struc -> {
VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10);
}).hide(empty).createRegion();
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
var graphic = entry.get()
.getProvider()
.getDisplayIconFileName(entry.get().getStore());
var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40);
view.padding(new Insets(2, 8, 2, 8));
var content = JfxHelper.createNamedEntry(
DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
var disable = new SimpleBooleanProperty();
return new ButtonComp(null, content, () -> {
ThreadHelper.runAsync(() -> {
model.restoreStateAsync(e, disable);
});
})
.accessibleText(DataStorage.get().getStoreDisplayName(entry.get()))
.disable(disable)
.styleClass("color-listBox")
.apply(struc -> struc.get().setMaxWidth(2000))
.grow(true, false);
})
.apply(struc -> {
VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10);
})
.hide(empty)
.createRegion();
var layout = new VBox();
layout.getStyleClass().add("welcome");
@ -107,9 +123,12 @@ public class BrowserWelcomeComp extends SimpleComp {
layout.getChildren().add(Comp.separator().hide(empty).createRegion());
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
model.restoreState(state);
actionEvent.consume();
}).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions");
model.restoreState(state);
actionEvent.consume();
})
.grow(true, false)
.hide(empty)
.accessibleTextKey("restoreAllSessions");
layout.getChildren().add(tile.createRegion());
return layout;

View file

@ -20,6 +20,9 @@ import java.util.function.Consumer;
public class FileSystemHelper {
private static final int DEFAULT_BUFFER_SIZE = 16384;
private static FileSystem localFileSystem;
public static String adjustPath(OpenFileSystemModel model, String path) {
if (path == null) {
return null;
@ -120,7 +123,8 @@ public class FileSystemHelper {
}
if (!model.getFileSystem().directoryExists(path)) {
throw ErrorEvent.unreportable(new IllegalArgumentException(String.format("Directory %s does not exist", path)));
throw ErrorEvent.unreportable(
new IllegalArgumentException(String.format("Directory %s does not exist", path)));
}
try {
@ -131,8 +135,6 @@ public class FileSystemHelper {
}
}
private static FileSystem localFileSystem;
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
if (localFileSystem == null) {
localFileSystem = new LocalStore().createFileSystem();
@ -150,7 +152,8 @@ public class FileSystemHelper {
Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE);
}
public static void dropLocalFilesInto(FileSystem.FileEntry entry, List<Path> files, Consumer<BrowserTransferProgress> progress) throws Exception {
public static void dropLocalFilesInto(
FileSystem.FileEntry entry, List<Path> files, Consumer<BrowserTransferProgress> progress) throws Exception {
var entries = files.stream()
.map(path -> {
try {
@ -178,8 +181,11 @@ public class FileSystemHelper {
}
public static void dropFilesInto(
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy, Consumer<BrowserTransferProgress> progress
) throws Exception {
FileSystem.FileEntry target,
List<FileSystem.FileEntry> files,
boolean explicitCopy,
Consumer<BrowserTransferProgress> progress)
throws Exception {
if (files.isEmpty()) {
progress.accept(BrowserTransferProgress.empty());
return;
@ -204,7 +210,12 @@ public class FileSystemHelper {
}
private static void dropFileAcrossSameFileSystem(
FileSystem.FileEntry target, FileSystem.FileEntry source, boolean explicitCopy, AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice, boolean multiple) throws Exception {
FileSystem.FileEntry target,
FileSystem.FileEntry source,
boolean explicitCopy,
AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice,
boolean multiple)
throws Exception {
// Prevent dropping directory into itself
if (source.getPath().equals(target.getPath())) {
return;
@ -218,7 +229,8 @@ public class FileSystemHelper {
}
if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) {
throw ErrorEvent.unreportable(new IllegalArgumentException("Target directory " + targetFile + " does already exist"));
throw ErrorEvent.unreportable(
new IllegalArgumentException("Target directory " + targetFile + " does already exist"));
}
if (!handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple)) {
@ -232,7 +244,12 @@ public class FileSystemHelper {
}
}
private static void dropFileAcrossFileSystems(FileSystem.FileEntry target, FileSystem.FileEntry source, Consumer<BrowserTransferProgress> progress, AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice, boolean multiple)
private static void dropFileAcrossFileSystems(
FileSystem.FileEntry target,
FileSystem.FileEntry source,
Consumer<BrowserTransferProgress> progress,
AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice,
boolean multiple)
throws Exception {
if (target.getKind() != FileKind.DIRECTORY) {
throw new IllegalStateException("Target " + target.getPath() + " is not a directory");
@ -273,7 +290,8 @@ public class FileSystemHelper {
if (sourceFile.getKind() == FileKind.DIRECTORY) {
target.getFileSystem().mkdirs(targetFile);
} else if (sourceFile.getKind() == FileKind.FILE) {
if (!handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) {
if (!handleChoice(
lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) {
continue;
}
@ -282,7 +300,7 @@ public class FileSystemHelper {
try {
inputStream = sourceFile.getFileSystem().openInput(sourceFile.getPath());
outputStream = target.getFileSystem().openOutput(targetFile, source.getSize());
transfer(source,inputStream, outputStream,transferred, totalSize, progress);
transfer(source, inputStream, outputStream, transferred, totalSize, progress);
inputStream.transferTo(OutputStream.nullOutputStream());
} catch (Exception ex) {
if (inputStream != null) {
@ -325,7 +343,12 @@ public class FileSystemHelper {
progress.accept(BrowserTransferProgress.finished(source.getName(), totalSize.get()));
}
private static boolean handleChoice(AtomicReference<BrowserAlerts.FileConflictChoice> previous, FileSystem fileSystem, String target, boolean multiple) throws Exception {
private static boolean handleChoice(
AtomicReference<BrowserAlerts.FileConflictChoice> previous,
FileSystem fileSystem,
String target,
boolean multiple)
throws Exception {
if (previous.get() == BrowserAlerts.FileConflictChoice.CANCEL) {
return false;
}
@ -339,7 +362,6 @@ public class FileSystemHelper {
return false;
}
var choice = BrowserAlerts.showFileConflictAlert(target, multiple);
if (choice == BrowserAlerts.FileConflictChoice.CANCEL) {
previous.set(BrowserAlerts.FileConflictChoice.CANCEL);
@ -363,9 +385,14 @@ public class FileSystemHelper {
return true;
}
private static final int DEFAULT_BUFFER_SIZE = 16384;
private static void transfer(FileSystem.FileEntry source, InputStream inputStream, OutputStream outputStream, AtomicLong transferred, AtomicLong total, Consumer<BrowserTransferProgress> progress) throws IOException {
private static void transfer(
FileSystem.FileEntry source,
InputStream inputStream,
OutputStream outputStream,
AtomicLong transferred,
AtomicLong total,
Consumer<BrowserTransferProgress> progress)
throws IOException {
var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, source.getSize());
byte[] buffer = new byte[bs];
int read;

View file

@ -8,7 +8,10 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.*;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellOpenFunction;
import io.xpipe.core.store.*;
import io.xpipe.core.util.FailableConsumer;
import javafx.beans.binding.Bindings;
@ -27,21 +30,22 @@ import java.util.stream.Stream;
public final class OpenFileSystemModel {
private final DataStoreEntryRef<? extends FileSystemStore> entry;
private FileSystem fileSystem;
private final Property<String> filter = new SimpleStringProperty();
private final BrowserFileListModel fileList;
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final BrowserModel browserModel;
private OpenFileSystemSavedState savedState;
private OpenFileSystemCache cache;
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
private final BooleanProperty inOverview = new SimpleBooleanProperty();
private final String name;
private final String tooltip;
private final Property<BrowserTransferProgress> progress =
new SimpleObjectProperty<>(BrowserTransferProgress.empty());
private FileSystem fileSystem;
private OpenFileSystemSavedState savedState;
private OpenFileSystemCache cache;
private int customScriptsStartIndex;
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>(BrowserTransferProgress.empty());
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
this.browserModel = browserModel;
@ -57,7 +61,10 @@ public final class OpenFileSystemModel {
}
public boolean isBusy() {
return !progress.getValue().done() || (fileSystem != null && fileSystem.getShell().isPresent() && fileSystem.getShell().get().getLock().isLocked());
return !progress.getValue().done()
|| (fileSystem != null
&& fileSystem.getShell().isPresent()
&& fileSystem.getShell().get().getLock().isLocked());
}
private void startIfNeeded() {
@ -171,15 +178,13 @@ public final class OpenFileSystemModel {
var directory = currentPath.get();
var name = adjustedPath + " - " + entry.get().getName();
ThreadHelper.runFailableAsync(() -> {
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) {
if (ShellDialects.getStartableDialects().stream()
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) {
TerminalLauncher.open(
entry.getEntry(),
name,
directory,
fileSystem
.getShell()
.get()
.singularSubShell(ShellOpenFunction.of(adjustedPath)));
fileSystem.getShell().get().singularSubShell(ShellOpenFunction.of(adjustedPath)));
} else {
TerminalLauncher.open(
entry.getEntry(),
@ -303,7 +308,8 @@ public final class OpenFileSystemModel {
startIfNeeded();
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
if (fileSystem.directoryExists(abs)) {
throw ErrorEvent.unreportable(new IllegalStateException(String.format("Directory %s already exists", abs)));
throw ErrorEvent.unreportable(
new IllegalStateException(String.format("Directory %s already exists", abs)));
}
fileSystem.mkdirs(abs);
@ -378,7 +384,8 @@ public final class OpenFileSystemModel {
BooleanScope.execute(busy, () -> {
var fs = entry.getStore().createFileSystem();
if (fs.getShell().isPresent()) {
this.customScriptsStartIndex = fs.getShell().get().getInitCommands().size();
this.customScriptsStartIndex =
fs.getShell().get().getInitCommands().size();
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
}
fs.open();
@ -413,7 +420,8 @@ public final class OpenFileSystemModel {
BooleanScope.execute(busy, () -> {
if (fileSystem.getShell().isPresent()) {
var connection = fileSystem.getShell().get();
var name = (directory != null ? directory + " - " : "") + entry.get().getName();
var name = (directory != null ? directory + " - " : "")
+ entry.get().getName();
TerminalLauncher.open(entry.getEntry(), name, directory, connection);
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively

View file

@ -33,6 +33,83 @@ import java.util.stream.Collectors;
@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class)
public class OpenFileSystemSavedState {
private static final Timer TIMEOUT_TIMER = new Timer(true);
private static final int STORED = 10;
@Setter
private OpenFileSystemModel model;
private String lastDirectory;
@NonNull
private ObservableList<RecentEntry> recentDirectories;
public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
this.lastDirectory = lastDirectory;
this.recentDirectories = recentDirectories;
}
public OpenFileSystemSavedState() {
lastDirectory = null;
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
}
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
return new OpenFileSystemSavedState();
});
state.setModel(model);
return state;
}
public void save() {
if (model == null) {
return;
}
AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this);
}
public void cd(String dir) {
if (dir == null) {
lastDirectory = null;
return;
}
lastDirectory = dir;
// After 10 seconds
TIMEOUT_TIMER.schedule(
new TimerTask() {
@Override
public void run() {
// Synchronize with platform thread
Platform.runLater(() -> {
if (model.isClosed()) {
return;
}
if (Objects.equals(lastDirectory, dir)) {
updateRecent(dir);
save();
}
});
}
},
10000);
}
private void updateRecent(String dir) {
var without = FileNames.removeTrailingSlash(dir);
var with = FileNames.toDirectory(dir);
recentDirectories.removeIf(recentEntry ->
Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with));
var o = new RecentEntry(with, Instant.now());
if (recentDirectories.size() < STORED) {
recentDirectories.addFirst(o);
} else {
recentDirectories.removeLast();
recentDirectories.addFirst(o);
}
}
public static class Serializer extends StdSerializer<OpenFileSystemSavedState> {
protected Serializer() {
@ -79,14 +156,6 @@ public class OpenFileSystemSavedState {
}
}
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
return new OpenFileSystemSavedState();
});
state.setModel(model);
return state;
}
@Value
@Jacksonized
@Builder
@ -95,76 +164,4 @@ public class OpenFileSystemSavedState {
String directory;
Instant time;
}
@Setter
private OpenFileSystemModel model;
private String lastDirectory;
@NonNull
private ObservableList<RecentEntry> recentDirectories;
public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
this.lastDirectory = lastDirectory;
this.recentDirectories = recentDirectories;
}
private static final Timer TIMEOUT_TIMER = new Timer(true);
private static final int STORED = 10;
public OpenFileSystemSavedState() {
lastDirectory = null;
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
}
public void save() {
if (model == null) {
return;
}
AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this);
}
public void cd(String dir) {
if (dir == null) {
lastDirectory = null;
return;
}
lastDirectory = dir;
// After 10 seconds
TIMEOUT_TIMER.schedule(
new TimerTask() {
@Override
public void run() {
// Synchronize with platform thread
Platform.runLater(() -> {
if (model.isClosed()) {
return;
}
if (Objects.equals(lastDirectory, dir)) {
updateRecent(dir);
save();
}
});
}
},
10000);
}
private void updateRecent(String dir) {
var without = FileNames.removeTrailingSlash(dir);
var with = FileNames.toDirectory(dir);
recentDirectories.removeIf(recentEntry ->
Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with));
var o = new RecentEntry(with, Instant.now());
if (recentDirectories.size() < STORED) {
recentDirectories.addFirst(o);
} else {
recentDirectories.removeLast();
recentDirectories.addFirst(o);
}
}
}

View file

@ -39,7 +39,8 @@ public class StandaloneFileBrowser {
});
}
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
public static void openSingleFile(
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
var comp = new BrowserComp(model)

View file

@ -13,14 +13,6 @@ import java.util.ServiceLoader;
public interface BrowserAction {
enum Category {
CUSTOM,
OPEN,
NATIVE,
COPY_PASTE,
MUTATION
}
List<BrowserAction> ALL = new ArrayList<>();
static List<LeafAction> getFlattened(OpenFileSystemModel model, List<BrowserEntry> entries) {
@ -75,6 +67,14 @@ public interface BrowserAction {
return true;
}
enum Category {
CUSTOM,
OPEN,
NATIVE,
COPY_PASTE,
MUTATION
}
class Loader implements ModuleLayerLoader {
@Override

View file

@ -52,7 +52,8 @@ public interface LeafAction extends BrowserAction {
b.setDisable(!isActive(model, selected));
});
if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
if (getProFeatureId() != null
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
b.setDisable(true);
b.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
}
@ -83,7 +84,8 @@ public interface LeafAction extends BrowserAction {
mi.setMnemonicParsing(false);
mi.setDisable(!isActive(model, selected));
if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
if (getProFeatureId() != null
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
mi.setDisable(true);
mi.setText(mi.getText() + " (Pro)");
}

View file

@ -26,24 +26,28 @@ public abstract class MultiExecuteAction implements BranchAction {
for (BrowserEntry entry : entries) {
TerminalLauncher.open(
model.getEntry().getEntry(),
FilenameUtils.getBaseName(entry.getRawFileEntry().getPath()),
model.getCurrentDirectory() != null ? model.getCurrentDirectory().getPath() : null,
FilenameUtils.getBaseName(
entry.getRawFileEntry().getPath()),
model.getCurrentDirectory() != null
? model.getCurrentDirectory()
.getPath()
: null,
pc.command(createCommand(pc, model, entry)));
}
},
false);
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return AppPrefs.get().terminalType().getValue() != null;
}
@Override
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
var t = AppPrefs.get().terminalType().getValue();
return "in " + (t != null ? t.toTranslatedString() : "?");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return AppPrefs.get().terminalType().getValue() != null;
}
},
new LeafAction() {
@ -52,7 +56,8 @@ public abstract class MultiExecuteAction implements BranchAction {
model.withShell(
pc -> {
for (BrowserEntry entry : entries) {
var cmd = ApplicationHelper.createDetachCommand(pc, createCommand(pc, model, entry));
var cmd = ApplicationHelper.createDetachCommand(
pc, createCommand(pc, model, entry));
pc.command(cmd)
.withWorkingDirectory(model.getCurrentDirectory()
.getPath())

View file

@ -71,6 +71,12 @@ public interface DirectoryType {
});
}
String getId();
boolean matches(FileSystem.FileEntry entry);
String getIcon(FileSystem.FileEntry entry, boolean open);
class Simple implements DirectoryType {
@Getter
@ -101,10 +107,4 @@ public interface DirectoryType {
return open ? this.open.getIcon() : this.closed.getIcon();
}
}
String getId();
boolean matches(FileSystem.FileEntry entry);
String getIcon(FileSystem.FileEntry entry, boolean open);
}

View file

@ -53,6 +53,12 @@ public interface FileType {
});
}
String getId();
boolean matches(FileSystem.FileEntry entry);
String getIcon();
@Getter
class Simple implements FileType {
@ -72,7 +78,9 @@ public interface FileType {
return false;
}
return (entry.getExtension() != null && endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT))) || endings.contains(entry.getName());
return (entry.getExtension() != null
&& endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT)))
|| endings.contains(entry.getName());
}
@Override
@ -80,10 +88,4 @@ public interface FileType {
return icon.getIcon();
}
}
String getId();
boolean matches(FileSystem.FileEntry entry);
String getIcon();
}

View file

@ -51,7 +51,10 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
buttons.setSpacing(5);
buttons.setAlignment(Pos.CENTER_RIGHT);
buttons.getChildren().addAll(customButtons().stream().map(buttonComp -> buttonComp.createRegion()).toList());
buttons.getChildren()
.addAll(customButtons().stream()
.map(buttonComp -> buttonComp.createRegion())
.toList());
var nextButton = new ButtonComp(AppI18n.observable("finishStep"), null, this::finish)
.apply(struc -> struc.get().setDefaultButton(true))
.styleClass(Styles.ACCENT)

View file

@ -68,8 +68,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
SimpleChangeListener.apply(currentValue, n -> {
PlatformThread.runLaterIfNeeded(() -> {
// Check if control value is the same. Then don't set it as that might cause bugs
if (Objects.equals(r.getText(), n)
|| (n == null && r.getText().isEmpty())) {
if (Objects.equals(r.getText(), n) || (n == null && r.getText().isEmpty())) {
return;
}

View file

@ -66,7 +66,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
return new SimpleCompStructure<>(scroll);
}
private void refresh(VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
private void refresh(
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
Runnable update = () -> {
// Clear cache of unused values
cache.keySet().removeIf(t -> !all.contains(t));

View file

@ -65,7 +65,8 @@ public class ListSelectorComp<T> extends SimpleComp {
if (showAllSelector) {
var allSelector = new CheckBox(null);
allSelector.setSelected(values.stream().filter(t -> !disable.test(t)).count() == selected.size());
allSelector.setSelected(
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
cbs.forEach(checkBox -> {
if (checkBox.isDisabled()) {

View file

@ -18,21 +18,19 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
private static final double FPS = 30.0;
private static final double cycleDurationSeconds = 4.0;
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
}
private final Comp<?> comp;
private final ObservableValue<Boolean> showLoading;
private final ObservableValue<Number> progress;
public LoadingOverlayComp(Comp<?> comp, ObservableValue<Boolean> loading, ObservableValue<Number> progress) {
this.comp = comp;
this.showLoading = PlatformThread.sync(loading);
this.progress = PlatformThread.sync(progress);
}
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
}
@Override
public CompStructure<StackPane> createBase() {
var compStruc = comp.createStructure();
@ -42,10 +40,10 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
loading.progressProperty().bind(progress);
loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode()));
// var pane = new StackPane();
// Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode();
// pane.getChildren().add(node);
// pane.setAlignment(Pos.CENTER);
// var pane = new StackPane();
// Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode();
// pane.getChildren().add(node);
// pane.setAlignment(Pos.CENTER);
var loadingOverlay = new StackPane(loading);
loadingOverlay.getStyleClass().add("loading-comp");

View file

@ -46,11 +46,14 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
@SneakyThrows
private WebView createWebView() {
var wv = new WebView();
wv.getEngine().setUserDataDirectory(AppProperties.get().getDataDir().resolve("webview").toFile());
wv.getEngine()
.setUserDataDirectory(
AppProperties.get().getDataDir().resolve("webview").toFile());
wv.setPageFill(Color.TRANSPARENT);
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark() ? "web/github-markdown-dark.css" : "web/github-markdown-light.css";
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme)
.orElseThrow();
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark()
? "web/github-markdown-dark.css"
: "web/github-markdown-light.css";
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
wv.getEngine().setUserStyleSheetLocation(url.toString());
SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> {

View file

@ -20,23 +20,14 @@ import lombok.Value;
public class ModalOverlayComp extends SimpleComp {
private final Comp<?> background;
private final Property<OverlayContent> overlayContent;
public ModalOverlayComp(Comp<?> background, Property<OverlayContent> overlayContent) {
this.background = background;
this.overlayContent = overlayContent;
}
@Value
public static class OverlayContent {
String titleKey;
Comp<?> content;
String finishKey;
Runnable onFinish;
}
private final Comp<?> background;
private final Property<OverlayContent> overlayContent;
@Override
protected Region createSimple() {
var bgRegion = background.createRegion();
@ -93,4 +84,13 @@ public class ModalOverlayComp extends SimpleComp {
});
return pane;
}
@Value
public static class OverlayContent {
String titleKey;
Comp<?> content;
String finishKey;
Runnable onFinish;
}
}

View file

@ -20,13 +20,15 @@ import java.util.Map;
public class OsLogoComp extends SimpleComp {
private static final Map<String, String> ICONS = new HashMap<>();
private static final String LINUX_DEFAULT = "linux-24.png";
private static final String LINUX_DEFAULT_SVG = "linux.svg";
private final StoreEntryWrapper wrapper;
private final ObservableValue<SystemStateComp.State> state;
public OsLogoComp(StoreEntryWrapper wrapper) {
this(wrapper, new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));
}
public OsLogoComp(StoreEntryWrapper wrapper, ObservableValue<SystemStateComp.State> state) {
this.wrapper = wrapper;
this.state = state;
@ -47,7 +49,8 @@ public class OsLogoComp extends SimpleComp {
return getImage(ons.getOsName());
},
wrapper.getPersistentState(), state));
wrapper.getPersistentState(),
state));
var hide = BindingsHelper.map(img, s -> s != null);
return new StackComp(List.of(
new SystemStateComp(state).hide(hide),
@ -55,10 +58,6 @@ public class OsLogoComp extends SimpleComp {
.createRegion();
}
private static final Map<String, String> ICONS = new HashMap<>();
private static final String LINUX_DEFAULT = "linux-24.png";
private static final String LINUX_DEFAULT_SVG = "linux.svg";
private String getImage(String name) {
if (name == null) {
return null;
@ -67,15 +66,21 @@ public class OsLogoComp extends SimpleComp {
if (ICONS.isEmpty()) {
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
try (var list = Files.list(file)) {
list.filter(path -> path.toString().endsWith(".svg") && !path.toString().endsWith(LINUX_DEFAULT_SVG))
.map(path -> FileNames.getFileName(path.toString())).forEach(path -> {
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
});
list.filter(path -> path.toString().endsWith(".svg")
&& !path.toString().endsWith(LINUX_DEFAULT_SVG))
.map(path -> FileNames.getFileName(path.toString()))
.forEach(path -> {
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
});
}
});
}
return ICONS.entrySet().stream().filter(e->name.toLowerCase().contains(e.getKey())).findAny().map(e->e.getValue()).orElse("os/" + LINUX_DEFAULT);
return ICONS.entrySet().stream()
.filter(e -> name.toLowerCase().contains(e.getKey()))
.findAny()
.map(e -> e.getValue())
.orElse("os/" + LINUX_DEFAULT);
}
}

View file

@ -40,22 +40,31 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
var vbox = new VBox();
vbox.setFillWidth(true);
var selectedBorder = Bindings.createObjectBinding(() -> {
var c = Platform.getPreferences().getAccentColor();
return new Border(new BorderStroke(c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
new BorderWidths(0, 3, 0, 0)));
}, Platform.getPreferences().accentColorProperty());
var selectedBorder = Bindings.createObjectBinding(
() -> {
var c = Platform.getPreferences().getAccentColor();
return new Border(new BorderStroke(
c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0)));
},
Platform.getPreferences().accentColorProperty());
var hoverBorder = Bindings.createObjectBinding(() -> {
var c = Platform.getPreferences().getAccentColor().darker();
return new Border(new BorderStroke(c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
new BorderWidths(0, 3, 0, 0)));
}, Platform.getPreferences().accentColorProperty());
var hoverBorder = Bindings.createObjectBinding(
() -> {
var c = Platform.getPreferences().getAccentColor().darker();
return new Border(new BorderStroke(
c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0)));
},
Platform.getPreferences().accentColorProperty());
var noneBorder = Bindings.createObjectBinding(() -> {
return new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
new BorderWidths(0, 3, 0, 0)));
}, Platform.getPreferences().accentColorProperty());
var noneBorder = Bindings.createObjectBinding(
() -> {
return new Border(new BorderStroke(
Color.TRANSPARENT,
BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,
new BorderWidths(0, 3, 0, 0)));
},
Platform.getPreferences().accentColorProperty());
var selected = PseudoClass.getPseudoClass("selected");
entries.forEach(e -> {
@ -68,43 +77,59 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
struc.get().pseudoClassStateChanged(selected, n.equals(e));
});
});
struc.get().borderProperty().bind(Bindings.createObjectBinding(() -> {
if (value.getValue().equals(e)) {
return selectedBorder.get();
}
struc.get()
.borderProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (value.getValue().equals(e)) {
return selectedBorder.get();
}
if (struc.get().isHover()) {
return hoverBorder.get();
}
if (struc.get().isHover()) {
return hoverBorder.get();
}
return noneBorder.get();
}, struc.get().hoverProperty(), value, hoverBorder, selectedBorder, noneBorder));
return noneBorder.get();
},
struc.get().hoverProperty(),
value,
hoverBorder,
selectedBorder,
noneBorder));
});
b.accessibleText(e.name());
vbox.getChildren().add(b.createRegion());
});
Augment<CompStructure<Button>> simpleBorders = struc -> {
struc.get().borderProperty().bind(Bindings.createObjectBinding(() -> {
if (struc.get().isHover()) {
return hoverBorder.get();
}
struc.get()
.borderProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (struc.get().isHover()) {
return hoverBorder.get();
}
return noneBorder.get();
}, struc.get().hoverProperty(), value, hoverBorder, selectedBorder, noneBorder));
return noneBorder.get();
},
struc.get().hoverProperty(),
value,
hoverBorder,
selectedBorder,
noneBorder));
};
{
var b = new IconButtonComp(
"mdal-bug_report",
() -> {
var b = new IconButtonComp("mdal-bug_report", () -> {
var event = ErrorEvent.fromMessage("User Report");
if (AppLogs.get().isWriteToFile()) {
event.attachment(AppLogs.get().getSessionLogsDirectory());
}
UserReportComp.show(event.build());
})
.apply(new FancyTooltipAugment<>("reportIssue")).apply(simpleBorders).accessibleTextKey("reportIssue");
.apply(new FancyTooltipAugment<>("reportIssue"))
.apply(simpleBorders)
.accessibleTextKey("reportIssue");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -113,7 +138,9 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
.apply(new FancyTooltipAugment<>("visitGithubRepository")).apply(simpleBorders).accessibleTextKey("visitGithubRepository");
.apply(new FancyTooltipAugment<>("visitGithubRepository"))
.apply(simpleBorders)
.accessibleTextKey("visitGithubRepository");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -122,7 +149,9 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
.apply(new FancyTooltipAugment<>("discord")).apply(simpleBorders).accessibleTextKey("discord");
.apply(new FancyTooltipAugment<>("discord"))
.apply(simpleBorders)
.accessibleTextKey("discord");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -131,7 +160,8 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
.apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip");
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"))
.accessibleTextKey("updateAvailableTooltip");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});

View file

@ -15,6 +15,7 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
private final Comp<?> center;
private Double initialWidth;
private Consumer<Double> onDividerChange;
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
this.left = left;
this.center = center;

View file

@ -18,29 +18,12 @@ import org.kordamp.ikonli.javafx.StackedFontIcon;
@Getter
public class SystemStateComp extends SimpleComp {
private final ObservableValue<State> state;
public SystemStateComp(ObservableValue<State> state) {
this.state = state;
}
public enum State {
FAILURE,
SUCCESS,
OTHER;
public static ObservableValue<State> shellState(StoreEntryWrapper w) {
return BindingsHelper.map(w.getPersistentState(),o -> {
if (o instanceof ShellStoreState shellStoreState) {
return shellStoreState.getRunning() != null ? shellStoreState.getRunning() ? SUCCESS : FAILURE : OTHER;
}
return OTHER;
});
}
}
private final ObservableValue<State> state;
@Override
protected Region createSimple() {
var icon = PlatformThread.sync(Bindings.createStringBinding(
@ -58,15 +41,19 @@ public class SystemStateComp extends SimpleComp {
border.getStyleClass().add("outer-icon");
border.setOpacity(0.5);
var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
var failure = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
var other = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
var success = Styles.toDataURI(
".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
var failure =
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
var other =
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
var pane = new StackedFontIcon();
pane.getChildren().addAll(fi, border);
pane.setAlignment(Pos.CENTER);
var dataClass1 = """
var dataClass1 =
"""
.stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 22px;
}
@ -78,9 +65,27 @@ public class SystemStateComp extends SimpleComp {
SimpleChangeListener.apply(PlatformThread.sync(state), val -> {
pane.getStylesheets().removeAll(success, failure, other);
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other);
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure : other);
});
return pane;
}
public enum State {
FAILURE,
SUCCESS,
OTHER;
public static ObservableValue<State> shellState(StoreEntryWrapper w) {
return BindingsHelper.map(w.getPersistentState(), o -> {
if (o instanceof ShellStoreState shellStoreState) {
return shellStoreState.getRunning() != null
? shellStoreState.getRunning() ? SUCCESS : FAILURE
: OTHER;
}
return OTHER;
});
}
}
}

View file

@ -28,6 +28,17 @@ import java.util.function.Consumer;
@Getter
public class TileButtonComp extends Comp<TileButtonComp.Structure> {
private final ObservableValue<String> name;
private final ObservableValue<String> description;
private final ObservableValue<String> icon;
private final Consumer<ActionEvent> action;
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
this.name = AppI18n.observable(nameKey);
this.description = AppI18n.observable(descriptionKey);
this.icon = new SimpleStringProperty(icon);
this.action = action;
}
@Override
public Structure createBase() {
var bt = new Button();
@ -68,7 +79,13 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
fi.setIconSize((int) (size * 0.55));
});
bt.setGraphic(hbox);
return Structure.builder().graphic(fi).button(bt).content(hbox).name(header).description(desc).build();
return Structure.builder()
.graphic(fi)
.button(bt)
.content(hbox)
.name(header)
.description(desc)
.build();
}
@Value
@ -85,16 +102,4 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
return button;
}
}
private final ObservableValue<String> name;
private final ObservableValue<String> description;
private final ObservableValue<String> icon;
private final Consumer<ActionEvent> action;
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
this.name = AppI18n.observable(nameKey);
this.description = AppI18n.observable(descriptionKey);
this.icon = new SimpleStringProperty(icon);
this.action = action;
}
}

View file

@ -34,14 +34,22 @@ public class DenseStoreEntryComp extends StoreEntryComp {
var info = wrapper.getEntry().getProvider().informationString(wrapper);
var summary = wrapper.getSummary();
if (wrapper.getEntry().getProvider() != null) {
information.textProperty().bind(PlatformThread.sync(Bindings.createStringBinding(() -> {
var val = summary.getValue();
if (val != null && grid.isHover() && wrapper.getEntry().getProvider().alwaysShowSummary()) {
return val;
} else {
return info.getValue();
}
}, grid.hoverProperty(), info, summary)));
information
.textProperty()
.bind(PlatformThread.sync(Bindings.createStringBinding(
() -> {
var val = summary.getValue();
if (val != null
&& grid.isHover()
&& wrapper.getEntry().getProvider().alwaysShowSummary()) {
return val;
} else {
return info.getValue();
}
},
grid.hoverProperty(),
info,
summary)));
}
return information;
@ -52,9 +60,12 @@ public class DenseStoreEntryComp extends StoreEntryComp {
grid.setHgap(8);
var name = createName().createRegion();
name.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> {
return grid.getWidth() / 2.5;
}, grid.widthProperty()));
name.maxWidthProperty()
.bind(Bindings.createDoubleBinding(
() -> {
return grid.getWidth() / 2.5;
},
grid.widthProperty()));
if (showIcon) {
var storeIcon = createIcon(30, 24);

View file

@ -12,7 +12,6 @@ public class StandardStoreEntryComp extends StoreEntryComp {
super(entry, content);
}
protected Region createContent() {
var name = createName().createRegion();

View file

@ -59,8 +59,9 @@ public class StoreCategoryWrapper {
public StoreCategoryWrapper getParent() {
return StoreViewState.get().getCategories().stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
.findAny().orElse(null);
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
.findAny()
.orElse(null);
}
public boolean contains(DataStoreEntry entry) {
@ -97,8 +98,8 @@ public class StoreCategoryWrapper {
DataStoreCategory p = category;
if (newValue) {
while ((p = DataStorage.get()
.getStoreCategoryIfPresent(p.getParentCategory())
.orElse(null))
.getStoreCategoryIfPresent(p.getParentCategory())
.orElse(null))
!= null) {
p.setShare(true);
}
@ -124,10 +125,9 @@ public class StoreCategoryWrapper {
.getUuid()
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
.toList());
Optional.ofNullable(getParent())
.ifPresent(storeCategoryWrapper -> {
storeCategoryWrapper.update();
});
Optional.ofNullable(getParent()).ifPresent(storeCategoryWrapper -> {
storeCategoryWrapper.update();
});
}
public String getName() {

View file

@ -61,7 +61,8 @@ public class StoreCreationComp extends DialogComp {
boolean staticDisplay;
public StoreCreationComp(
Stage window, Consumer<DataStoreEntry> consumer,
Stage window,
Consumer<DataStoreEntry> consumer,
Property<DataStoreProvider> provider,
Property<DataStore> store,
Predicate<DataStoreProvider> filter,
@ -101,36 +102,42 @@ public class StoreCreationComp extends DialogComp {
newValue.validate();
});
});
this.entry = Bindings.createObjectBinding(() -> {
if (name.getValue() == null || store.getValue() == null) {
return null;
}
this.entry = Bindings.createObjectBinding(
() -> {
if (name.getValue() == null || store.getValue() == null) {
return null;
}
var testE = DataStoreEntry.createNew(
UUID.randomUUID(),
DataStorage.get().getSelectedCategory().getUuid(),
name.getValue(),
store.getValue());
var p = provider.getValue().getDisplayParent(testE);
var testE = DataStoreEntry.createNew(
UUID.randomUUID(),
DataStorage.get().getSelectedCategory().getUuid(),
name.getValue(),
store.getValue());
var p = provider.getValue().getDisplayParent(testE);
var targetCategory = p != null
? p.getCategoryUuid()
: DataStorage.get()
.getSelectedCategory()
.getUuid();
var rootCategory = DataStorage.get().getRootCategory(DataStorage.get().getStoreCategoryIfPresent(targetCategory).orElseThrow());
// Don't put connections in the scripts category ever
if ((provider.getValue().getCreationCategory() == null || !provider.getValue().getCreationCategory().equals(DataStoreProvider.CreationCategory.SCRIPT)) &&
rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
targetCategory = DataStorage.get().getDefaultConnectionsCategory().getUuid();
}
var targetCategory = p != null
? p.getCategoryUuid()
: DataStorage.get().getSelectedCategory().getUuid();
var rootCategory = DataStorage.get()
.getRootCategory(DataStorage.get()
.getStoreCategoryIfPresent(targetCategory)
.orElseThrow());
// Don't put connections in the scripts category ever
if ((provider.getValue().getCreationCategory() == null
|| !provider.getValue()
.getCreationCategory()
.equals(DataStoreProvider.CreationCategory.SCRIPT))
&& rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
targetCategory = DataStorage.get()
.getDefaultConnectionsCategory()
.getUuid();
}
return DataStoreEntry.createNew(
UUID.randomUUID(),
targetCategory,
name.getValue(),
store.getValue());
}, name, store);
return DataStoreEntry.createNew(
UUID.randomUUID(), targetCategory, name.getValue(), store.getValue());
},
name,
store);
}
public static void showEdit(DataStoreEntry e) {
@ -186,60 +193,18 @@ public class StoreCreationComp extends DialogComp {
DataStoreEntry existingEntry) {
var prop = new SimpleObjectProperty<>(provider);
var store = new SimpleObjectProperty<>(s);
DialogComp.showWindow("addConnection", stage -> new StoreCreationComp(
stage, con, prop, store, filter, initialName, existingEntry, staticDisplay));
}
@Override
protected List<Comp<?>> customButtons() {
return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> {
if (showInvalidConfirmAlert()) {
commit();
} else {
finish();
}
}).visible(skippable));
}
@Override
protected ObservableValue<Boolean> busy() {
return busy;
}
@Override
public Comp<?> bottom() {
var disable = Bindings.createBooleanBinding(
() -> {
return provider.getValue() == null
|| store.getValue() == null
|| !store.getValue().isComplete()
// When switching providers, both observables change one after another.
// So temporarily there might be a store class mismatch
|| provider.getValue().getStoreClasses().stream().noneMatch(aClass -> aClass.isAssignableFrom(store.getValue().getClass()))
|| provider.getValue().createInsightsMarkdown(store.getValue()) == null;
},
provider,
store);
return new PopupMenuButtonComp(
new SimpleStringProperty("Insights >"),
Comp.of(() -> {
return provider.getValue() != null
? provider.getValue()
.createInsightsComp(store)
.createRegion()
: null;
}),
true)
.hide(disable)
.styleClass("button-comp");
DialogComp.showWindow(
"addConnection",
stage -> new StoreCreationComp(
stage, con, prop, store, filter, initialName, existingEntry, staticDisplay));
}
private static boolean showInvalidConfirmAlert() {
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmInvalidStoreTitle"));
alert.setHeaderText(AppI18n.get("confirmInvalidStoreHeader"));
alert.getDialogPane().setContent(AppWindowHelper.alertContentText(
AppI18n.get("confirmInvalidStoreContent")));
alert.getDialogPane()
.setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent")));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE));
@ -249,29 +214,21 @@ public class StoreCreationComp extends DialogComp {
.orElse(false);
}
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
return new OptionsBuilder()
.addComp(comp, store)
.name("connectionName")
.description("connectionNameDescription")
.addString(name, false)
.nonNull(propVal)
.build();
@Override
protected List<Comp<?>> customButtons() {
return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> {
if (showInvalidConfirmAlert()) {
commit();
} else {
finish();
}
})
.visible(skippable));
}
private void commit() {
if (finished.get()) {
return;
}
finished.setValue(true);
if (entry.getValue() != null) {
consumer.accept(entry.getValue());
}
PlatformThread.runLaterIfNeeded(() -> {
window.close();
});
@Override
protected ObservableValue<Boolean> busy() {
return busy;
}
@Override
@ -336,6 +293,11 @@ public class StoreCreationComp extends DialogComp {
});
}
@Override
public Comp<?> content() {
return Comp.of(this::createLayout);
}
@Override
protected Comp<?> scrollPane(Comp<?> content) {
var back = super.scrollPane(content);
@ -343,8 +305,58 @@ public class StoreCreationComp extends DialogComp {
}
@Override
public Comp<?> content() {
return Comp.of(this::createLayout);
public Comp<?> bottom() {
var disable = Bindings.createBooleanBinding(
() -> {
return provider.getValue() == null
|| store.getValue() == null
|| !store.getValue().isComplete()
// When switching providers, both observables change one after another.
// So temporarily there might be a store class mismatch
|| provider.getValue().getStoreClasses().stream()
.noneMatch(aClass -> aClass.isAssignableFrom(
store.getValue().getClass()))
|| provider.getValue().createInsightsMarkdown(store.getValue()) == null;
},
provider,
store);
return new PopupMenuButtonComp(
new SimpleStringProperty("Insights >"),
Comp.of(() -> {
return provider.getValue() != null
? provider.getValue()
.createInsightsComp(store)
.createRegion()
: null;
}),
true)
.hide(disable)
.styleClass("button-comp");
}
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
return new OptionsBuilder()
.addComp(comp, store)
.name("connectionName")
.description("connectionNameDescription")
.addString(name, false)
.nonNull(propVal)
.build();
}
private void commit() {
if (finished.get()) {
return;
}
finished.setValue(true);
if (entry.getValue() != null) {
consumer.accept(entry.getValue());
}
PlatformThread.runLaterIfNeeded(() -> {
window.close();
});
}
private Region createLayout() {

View file

@ -24,33 +24,42 @@ public class StoreCreationMenu {
menu.getItems().add(automatically);
menu.getItems().add(new SeparatorMenuItem());
menu.getItems().add(category("addHost", "mdi2h-home-plus",
DataStoreProvider.CreationCategory.HOST, "ssh"));
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh"));
menu.getItems().add(category("addShell", "mdi2t-text-box-multiple",
DataStoreProvider.CreationCategory.SHELL, null));
menu.getItems()
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
menu.getItems().add(category("addScript", "mdi2s-script-text-outline",
DataStoreProvider.CreationCategory.SCRIPT, "script"));
menu.getItems()
.add(category(
"addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script"));
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than",
DataStoreProvider.CreationCategory.COMMAND, "cmd"));
menu.getItems()
.add(category(
"addCommand", "mdi2c-code-greater-than", DataStoreProvider.CreationCategory.COMMAND, "cmd"));
menu.getItems().add(category("addTunnel", "mdi2v-vector-polyline-plus",
DataStoreProvider.CreationCategory.TUNNEL, null));
menu.getItems()
.add(category(
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreProvider.CreationCategory.TUNNEL, null));
menu.getItems().add(category("addDatabase", "mdi2d-database-plus",
DataStoreProvider.CreationCategory.DATABASE, null));
menu.getItems()
.add(category("addDatabase", "mdi2d-database-plus", DataStoreProvider.CreationCategory.DATABASE, null));
}
private static MenuItem category(String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) {
var sub = DataStoreProviders.getAll().stream().filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())).toList();
private static MenuItem category(
String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) {
var sub = DataStoreProviders.getAll().stream()
.filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()))
.toList();
if (sub.size() < 2) {
var item = new MenuItem();
item.setGraphic(new FontIcon(graphic));
item.textProperty().bind(AppI18n.observable(name));
item.setOnAction(event -> {
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null, category);
StoreCreationComp.showCreation(
defaultProvider != null
? DataStoreProviders.byName(defaultProvider).orElseThrow()
: null,
category);
event.consume();
});
return item;
@ -64,16 +73,19 @@ public class StoreCreationMenu {
return;
}
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null,
StoreCreationComp.showCreation(
defaultProvider != null
? DataStoreProviders.byName(defaultProvider).orElseThrow()
: null,
category);
event.consume();
});
sub.forEach(dataStoreProvider -> {
var item = new MenuItem(dataStoreProvider.getDisplayName());
item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null)).createRegion());
item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null))
.createRegion());
item.setOnAction(event -> {
StoreCreationComp.showCreation(dataStoreProvider,
category);
StoreCreationComp.showCreation(dataStoreProvider, category);
event.consume();
});
menu.getItems().add(item);

View file

@ -40,9 +40,22 @@ import java.util.Arrays;
public abstract class StoreEntryComp extends SimpleComp {
public static StoreEntryComp create(
StoreEntryWrapper entry, Comp<?> content, boolean preferLarge) {
var forceCondensed = AppPrefs.get() != null && AppPrefs.get().condenseConnectionDisplay().get();
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
App.getApp().getStage().widthProperty().divide(2.2).add(-100);
public static final ObservableDoubleValue INFO_WITH_CONTENT_WIDTH =
App.getApp().getStage().widthProperty().divide(2.2).add(-200);
protected final StoreEntryWrapper wrapper;
protected final Comp<?> content;
public StoreEntryComp(StoreEntryWrapper wrapper, Comp<?> content) {
this.wrapper = wrapper;
this.content = content;
}
public static StoreEntryComp create(StoreEntryWrapper entry, Comp<?> content, boolean preferLarge) {
var forceCondensed = AppPrefs.get() != null
&& AppPrefs.get().condenseConnectionDisplay().get();
if (!preferLarge || forceCondensed) {
return new DenseStoreEntryComp(entry, true, content);
} else {
@ -55,27 +68,14 @@ public abstract class StoreEntryComp extends SimpleComp {
if (prov != null) {
return prov.customEntryComp(e, topLevel);
} else {
var forceCondensed = AppPrefs.get() != null && AppPrefs.get().condenseConnectionDisplay().get();
return forceCondensed ?
new DenseStoreEntryComp(e.getWrapper(), true, null) :
new StandardStoreEntryComp(e.getWrapper(), null);
var forceCondensed = AppPrefs.get() != null
&& AppPrefs.get().condenseConnectionDisplay().get();
return forceCondensed
? new DenseStoreEntryComp(e.getWrapper(), true, null)
: new StandardStoreEntryComp(e.getWrapper(), null);
}
}
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
App.getApp().getStage().widthProperty().divide(2.2).add(-100);
public static final ObservableDoubleValue INFO_WITH_CONTENT_WIDTH =
App.getApp().getStage().widthProperty().divide(2.2).add(-200);
protected final StoreEntryWrapper wrapper;
protected final Comp<?> content;
public StoreEntryComp(StoreEntryWrapper wrapper, Comp<?> content) {
this.wrapper = wrapper;
this.content = content;
}
@Override
protected final Region createSimple() {
var r = createContent();
@ -87,8 +87,7 @@ public abstract class StoreEntryComp extends SimpleComp {
button.setPadding(Insets.EMPTY);
button.setMaxWidth(5000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(wrapper.nameProperty());
button.accessibleTextProperty().bind(wrapper.nameProperty());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
@ -109,8 +108,13 @@ public abstract class StoreEntryComp extends SimpleComp {
protected Label createInformation() {
var information = new Label();
information.setGraphicTextGap(7);
information.textProperty().bind(wrapper.getEntry().getProvider() != null ?
PlatformThread.sync(wrapper.getEntry().getProvider().informationString(wrapper)) : new SimpleStringProperty());
information
.textProperty()
.bind(
wrapper.getEntry().getProvider() != null
? PlatformThread.sync(
wrapper.getEntry().getProvider().informationString(wrapper))
: new SimpleStringProperty());
information.getStyleClass().add("information");
AppFont.header(information);
@ -195,15 +199,16 @@ public abstract class StoreEntryComp extends SimpleComp {
continue;
}
var button = new IconButtonComp(
actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
var button =
new IconButtonComp(actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
wrapper.getEntry().ref());
action.execute();
});
});
button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.accessibleText(
actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(wrapper.getEntry().ref())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
@ -268,11 +273,13 @@ public abstract class StoreEntryComp extends SimpleComp {
? new Menu(null, new FontIcon(icon))
: new MenuItem(null, new FontIcon(icon));
var proRequired = p.getKey().getProFeatureId() != null &&
!LicenseProvider.get().getFeature(p.getKey().getProFeatureId()).isSupported();
var proRequired = p.getKey().getProFeatureId() != null
&& !LicenseProvider.get()
.getFeature(p.getKey().getProFeatureId())
.isSupported();
if (proRequired) {
item.setDisable(true);
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)",name));
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
} else {
item.textProperty().bind(name);
}
@ -289,8 +296,7 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.hide();
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
wrapper.getEntry().ref());
var action = actionProvider.createAction(wrapper.getEntry().ref());
action.execute();
});
});
@ -306,20 +312,27 @@ public abstract class StoreEntryComp extends SimpleComp {
run.textProperty().bind(AppI18n.observable("base.execute"));
run.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
p.getKey().getDataStoreCallSite().createAction(wrapper.getEntry().ref()).execute();
p.getKey()
.getDataStoreCallSite()
.createAction(wrapper.getEntry().ref())
.execute();
});
});
menu.getItems().add(run);
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
var url = "xpipe://action/" + p.getKey().getId() + "/"
+ wrapper.getEntry().getUuid();
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
sc.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
DesktopShortcuts.create(url,
wrapper.nameProperty().getValue() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().ref()).getValue() + ")");
DesktopShortcuts.create(
url,
wrapper.nameProperty().getValue() + " ("
+ p.getKey()
.getDataStoreCallSite()
.getName(wrapper.getEntry().ref())
.getValue() + ")");
});
});
menu.getItems().add(sc);
@ -349,20 +362,23 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(browse);
}
if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
if (wrapper.getEntry().getProvider() != null
&& wrapper.getEntry().getProvider().canMoveCategories()) {
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(storeCategoryWrapper.getName());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
if (storeCategoryWrapper.getParent() == null) {
m.setDisable(true);
}
StoreViewState.get()
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
.forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
if (storeCategoryWrapper.getParent() == null) {
m.setDisable(true);
}
move.getItems().add(m);
});
move.getItems().add(m);
});
contextMenu.getItems().add(move);
}
@ -386,9 +402,16 @@ public abstract class StoreEntryComp extends SimpleComp {
}
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(Bindings.createBooleanBinding(() -> {
return !wrapper.getDeletable().get() && !AppPrefs.get().developerDisableGuiRestrictions().get();
}, wrapper.getDeletable(), AppPrefs.get().developerDisableGuiRestrictions()));
del.disableProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return !wrapper.getDeletable().get()
&& !AppPrefs.get()
.developerDisableGuiRestrictions()
.get();
},
wrapper.getDeletable(),
AppPrefs.get().developerDisableGuiRestrictions()));
del.setOnAction(event -> wrapper.delete());
contextMenu.getItems().add(del);

View file

@ -35,10 +35,18 @@ public class StoreEntryListComp extends SimpleComp {
var showIntro = Bindings.createBooleanBinding(
() -> {
var all = StoreViewState.get().getAllConnectionsCategory();
var connections = StoreViewState.get().getAllEntries().stream().filter(wrapper -> all.contains(wrapper.getEntry())).toList();
return initialCount == connections.size() && StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory());
var connections = StoreViewState.get().getAllEntries().stream()
.filter(wrapper -> all.contains(wrapper.getEntry()))
.toList();
return initialCount == connections.size()
&& StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(StoreViewState.get().getAllConnectionsCategory());
},
StoreViewState.get().getAllEntries(), StoreViewState.get().getActiveCategory());
StoreViewState.get().getAllEntries(),
StoreViewState.get().getActiveCategory());
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
map.put(
createList(),

View file

@ -51,16 +51,29 @@ public class StoreEntryListStatusComp extends SimpleComp {
private Region createGroupListHeader() {
var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "Connections" : "Scripts";
}, StoreViewState.get().getActiveCategory()));
label.textProperty()
.bind(Bindings.createStringBinding(
() -> {
return StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(StoreViewState.get().getAllConnectionsCategory())
? "Connections"
: "Scripts";
},
StoreViewState.get().getActiveCategory()));
label.getStyleClass().add("name");
var all = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> {
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(storeRoot);
return StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(storeRoot);
},
StoreViewState.get().getActiveCategory());
var shownList = BindingsHelper.filteredContentBinding(
@ -73,7 +86,13 @@ public class StoreEntryListStatusComp extends SimpleComp {
var count = new CountComp<>(shownList, all);
var c = count.createRegion();
var topBar = new HBox(label, c, Comp.hspacer().createRegion(), createDateSortButton().createRegion(), Comp.hspacer(2).createRegion(), createAlphabeticalSortButton().createRegion());
var topBar = new HBox(
label,
c,
Comp.hspacer().createRegion(),
createDateSortButton().createRegion(),
Comp.hspacer(2).createRegion(),
createAlphabeticalSortButton().createRegion());
AppFont.setSize(label, 3);
AppFont.setSize(c, 3);
topBar.setAlignment(Pos.CENTER);
@ -94,7 +113,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
});
filter.apply(struc -> struc.get().sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
struc.getText().requestFocus();
struc.getText().requestFocus();
}
}));
@ -111,7 +130,6 @@ public class StoreEntryListStatusComp extends SimpleComp {
f.setPadding(new Insets(-3, 0, -3, 0));
}
AppFont.medium(hbox);
return hbox;
}

View file

@ -121,7 +121,10 @@ public class StoreEntryWrapper {
deletable.setValue(entry.getConfiguration().isDeletable()
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
category.setValue(StoreViewState.get().getCategoryWrapper(DataStorage.get().getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow()));
category.setValue(StoreViewState.get()
.getCategoryWrapper(DataStorage.get()
.getStoreCategoryIfPresent(entry.getCategoryUuid())
.orElseThrow()));
if (!entry.getValidity().isUsable()) {
summary.setValue(null);
@ -155,8 +158,7 @@ public class StoreEntryWrapper {
&& e.getDefaultDataStoreCallSite()
.getApplicableClass()
.isAssignableFrom(entry.getStore().getClass())
&& e.getDefaultDataStoreCallSite()
.isApplicable(entry.ref()))
&& e.getDefaultDataStoreCallSite().isApplicable(entry.ref()))
.findFirst()
.map(ActionProvider::getDefaultDataStoreCallSite)
.orElse(null);

View file

@ -35,7 +35,8 @@ public class StoreIntroComp extends SimpleComp {
var scanPane = new StackPane(scanButton);
scanPane.setAlignment(Pos.CENTER);
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion();
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150)
.createRegion();
var text = new VBox(title, introDesc);
text.setSpacing(5);
text.setAlignment(Pos.CENTER_LEFT);

View file

@ -19,10 +19,12 @@ public class StoreLayoutComp extends SimpleComp {
@Override
protected Region createSimple() {
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
}).createStructure();
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
.withOnDividerChange(aDouble -> {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
})
.createStructure();
struc.getLeft().setMinWidth(260);
struc.getLeft().setMaxWidth(500);
struc.get().getStyleClass().add("store-layout");

View file

@ -28,7 +28,9 @@ public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataSto
boolean staticDisplay;
private List<DataStoreProvider> getProviders() {
return DataStoreProviders.getAll().stream().filter(val -> filter == null || filter.test(val)).toList();
return DataStoreProviders.getAll().stream()
.filter(val -> filter == null || filter.test(val))
.toList();
}
private Region createGraphic(DataStoreProvider provider) {

View file

@ -19,21 +19,11 @@ import java.util.function.Predicate;
@Value
public class StoreSection {
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customSectionComp(e, topLevel);
} else {
return new StoreSectionComp(e, topLevel);
}
}
StoreEntryWrapper wrapper;
ObservableList<StoreSection> allChildren;
ObservableList<StoreSection> shownChildren;
int depth;
ObservableBooleanValue showDetails;
public StoreSection(
StoreEntryWrapper wrapper,
ObservableList<StoreSection> allChildren,
@ -55,6 +45,15 @@ public class StoreSection {
}
}
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customSectionComp(e, topLevel);
} else {
return new StoreSectionComp(e, topLevel);
}
}
private static ObservableList<StoreSection> sorted(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
if (category == null) {
@ -63,7 +62,9 @@ public class StoreSection {
var c = Comparator.<StoreSection>comparingInt(
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
var mappedSortMode = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
var mappedSortMode = BindingsHelper.mappedBinding(
category,
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
return BindingsHelper.orderedContentBinding(
list,
(o1, o2) -> {
@ -97,7 +98,9 @@ public class StoreSection {
section -> {
var showFilter = filterString == null || section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
var sameCategory = category == null
|| category.getValue() == null
|| category.getValue().contains(section.getWrapper().getEntry());
return showFilter && matchesSelector && sameCategory;
},
category,
@ -118,10 +121,10 @@ public class StoreSection {
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
@ -134,9 +137,13 @@ public class StoreSection {
section -> {
var showFilter = filterString == null || section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
// If this entry is already shown as root due to a different category than parent, don't show it again here
var notRoot = !DataStorage.get().isRootEntry(section.getWrapper().getEntry());
var sameCategory = category == null
|| category.getValue() == null
|| category.getValue().contains(section.getWrapper().getEntry());
// If this entry is already shown as root due to a different category than parent, don't show it
// again here
var notRoot =
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
return showFilter && matchesSelector && sameCategory && notRoot;
},
category,

View file

@ -22,12 +22,11 @@ import java.util.List;
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private final StoreSection section;
private final boolean topLevel;
@ -54,9 +53,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(30))
.apply(struc -> struc.get().setPrefWidth(30))
.focusTraversable()
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.accessibleText(Bindings.createStringBinding(
() -> {
return "Expand " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getShownChildren()).isEqualTo(0)))
.grow(false, true)

View file

@ -28,19 +28,17 @@ import java.util.function.BiConsumer;
@Builder
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
private final StoreSection section;
@Builder.Default
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (section1, buttonComp) -> {};
public static Comp<?> createList(StoreSection top, BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment) {
return new StoreSectionMiniComp(top, augment);
}
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private final StoreSection section;
@Builder.Default
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (section1, buttonComp) -> {};
@Override
public CompStructure<VBox> createBase() {
var list = new ArrayList<Comp<?>>();
@ -48,14 +46,14 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
if (section.getWrapper() != null) {
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
.apply(struc -> {
var provider = section.getWrapper()
.getEntry()
.getProvider();
var provider = section.getWrapper().getEntry().getProvider();
struc.get()
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(provider != null ? provider
.getDisplayIconFileName(section.getWrapper()
.getEntry()
.getStore()) : null)
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(
provider != null
? provider.getDisplayIconFileName(section.getWrapper()
.getEntry()
.getStore())
: null)
.createRegion());
})
.apply(struc -> {
@ -79,38 +77,47 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(20))
.apply(struc -> struc.get().setPrefWidth(20))
.focusTraversable()
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.accessibleText(Bindings.createStringBinding(
() -> {
return "Expand "
+ section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getAllChildren()).isEqualTo(0)))
.grow(false, true)
.styleClass("expand-button");
List<Comp<?>> topEntryList = List.of(button, root);
list.add(new HorizontalComp(topEntryList)
.apply(struc -> struc.get().setFillHeight(true)));
list.add(new HorizontalComp(topEntryList).apply(struc -> struc.get().setFillHeight(true)));
} else {
expanded = new SimpleBooleanProperty(true);
}
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getWrapper() != null ? BindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20
|| expanded.get(),
expanded,
section.getAllChildren()) : section.getShownChildren();
var listSections = section.getWrapper() != null
? BindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
expanded,
section.getAllChildren())
: section.getShownChildren();
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
return StoreSectionMiniComp.builder().section(e).augment(this.augment).build();
}).withLimit(100).minHeight(0).hgrow();
return StoreSectionMiniComp.builder()
.section(e)
.augment(this.augment)
.build();
})
.withLimit(100)
.minHeight(0)
.hgrow();
list.add(new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren()).isEqualTo(0)))));
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren()).isEqualTo(0)))));
return new VerticalComp(list)
.styleClass("store-section-mini-comp")
@ -130,8 +137,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
return;
}
struc.get().getStyleClass().removeIf(
s -> Arrays.stream(DataStoreColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values())
.anyMatch(dataStoreColor ->
dataStoreColor.getId().equals(s)));
struc.get().getStyleClass().remove("none");
struc.get().getStyleClass().add("color-box");
if (val != null) {
@ -141,7 +149,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
}
});
}
})
})
.createStructure();
}
}

View file

@ -14,9 +14,18 @@ public class StoreSidebarComp extends SimpleComp {
protected Region createSimple() {
var sideBar = new VerticalComp(List.of(
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()).styleClass("color-box").styleClass("gray"),
Comp.of(() -> new Region()).styleClass("bar").styleClass("color-box").styleClass("gray").styleClass("filler-bar").vgrow()));
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory())
.styleClass("color-box")
.styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory())
.styleClass("color-box")
.styleClass("gray"),
Comp.of(() -> new Region())
.styleClass("bar")
.styleClass("color-box")
.styleClass("gray")
.styleClass("filler-bar")
.vgrow()));
sideBar.apply(struc -> struc.get().setFillWidth(true));
sideBar.styleClass("sidebar");
sideBar.prefWidth(240);

View file

@ -11,8 +11,6 @@ import java.util.stream.Stream;
public interface StoreSortMode {
StoreSection representative(StoreSection s);
StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
@Override
public StoreSection representative(StoreSection s) {
@ -30,7 +28,6 @@ public interface StoreSortMode {
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT));
}
};
StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
@Override
public StoreSection representative(StoreSection s) {
@ -49,14 +46,19 @@ public interface StoreSortMode {
.reversed();
}
};
StoreSortMode DATE_DESC = new StoreSortMode() {
@Override
public StoreSection representative(StoreSection s) {
var c = comparator();
return Stream.of(s.getShownChildren().stream().max((o1, o2) -> {
return c.compare(representative(o1), representative(o2));
}).orElse(s), s).max(c).orElseThrow();
return Stream.of(
s.getShownChildren().stream()
.max((o1, o2) -> {
return c.compare(representative(o1), representative(o2));
})
.orElse(s),
s)
.max(c)
.orElseThrow();
}
@Override
@ -74,14 +76,19 @@ public interface StoreSortMode {
});
}
};
StoreSortMode DATE_ASC = new StoreSortMode() {
@Override
public StoreSection representative(StoreSection s) {
var c = comparator();
return Stream.of(s.getShownChildren().stream().min((o1, o2) -> {
return c.compare(representative(o1), representative(o2));
}).orElse(s), s).min(c).orElseThrow();
return Stream.of(
s.getShownChildren().stream()
.min((o1, o2) -> {
return c.compare(representative(o1), representative(o2));
})
.orElse(s),
s)
.min(c)
.orElseThrow();
}
@Override
@ -92,13 +99,15 @@ public interface StoreSortMode {
@Override
public Comparator<StoreSection> comparator() {
return Comparator.<StoreSection, Instant>comparing(e -> {
return flatten(e)
.map(entry -> entry.getLastAccess())
.max(Comparator.naturalOrder())
.orElseThrow();
}).reversed();
return flatten(e)
.map(entry -> entry.getLastAccess())
.max(Comparator.naturalOrder())
.orElseThrow();
})
.reversed();
}
};
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
static Stream<DataStoreEntry> flatten(StoreSection section) {
return Stream.concat(
@ -106,14 +115,14 @@ public interface StoreSortMode {
section.getAllChildren().stream().flatMap(section1 -> flatten(section1)));
}
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
static Optional<StoreSortMode> fromId(String id) {
return ALL.stream()
.filter(storeSortMode -> storeSortMode.getId().equals(id))
.findFirst();
}
StoreSection representative(StoreSection s);
String getId();
Comparator<StoreSection> comparator();

View file

@ -24,6 +24,22 @@ import java.util.stream.Collectors;
public class StoreViewState {
private static StoreViewState INSTANCE;
private final StringProperty filter = new SimpleStringProperty();
@Getter
private final ObservableList<StoreEntryWrapper> allEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final ObservableList<StoreCategoryWrapper> categories =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
@Getter
private StoreSection currentTopLevelSection;
private StoreViewState() {
initContent();
addListeners();
}
public static void init() {
if (INSTANCE != null) {
@ -53,27 +69,6 @@ public class StoreViewState {
return INSTANCE;
}
private final StringProperty filter = new SimpleStringProperty();
@Getter
private final ObservableList<StoreEntryWrapper> allEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final ObservableList<StoreCategoryWrapper> categories =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private StoreSection currentTopLevelSection;
@Getter
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
private StoreViewState() {
initContent();
addListeners();
}
private void updateContent() {
categories.forEach(c -> c.update());
allEntries.forEach(e -> e.update());
@ -126,12 +121,14 @@ public class StoreViewState {
});
}
// Watch out for synchronizing all calls to the entries and categories list!
DataStorage.get().addListener(new StorageListener() {
@Override
public void onStoreAdd(DataStoreEntry... entry) {
var l = Arrays.stream(entry).map(StoreEntryWrapper::new).peek(storeEntryWrapper -> storeEntryWrapper.update()).toList();
var l = Arrays.stream(entry)
.map(StoreEntryWrapper::new)
.peek(storeEntryWrapper -> storeEntryWrapper.update())
.toList();
Platform.runLater(() -> {
// Don't update anything if we have already reset
if (INSTANCE == null) {

View file

@ -42,7 +42,8 @@ public class App extends Application {
if (OsType.getLocal().equals(OsType.LINUX)) {
try {
Toolkit xToolkit = Toolkit.getDefaultToolkit();
java.lang.reflect.Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
java.lang.reflect.Field awtAppClassNameField =
xToolkit.getClass().getDeclaredField("awtAppClassName");
awtAppClassNameField.setAccessible(true);
awtAppClassNameField.set(xToolkit, "XPipe");
} catch (Exception e) {
@ -92,5 +93,4 @@ public class App extends Application {
stage.requestFocus();
});
}
}

View file

@ -16,7 +16,8 @@ public class AppBundledFonts {
return;
}
System.setProperty("prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
System.setProperty(
"prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
System.setProperty("prism.embeddedfonts", "true");
}

View file

@ -11,8 +11,9 @@ public class AppDebugModeNotice {
}
var out = AppLogs.get().getOriginalSysOut();
var msg = """
var msg =
"""
****************************************
* You are running XPipe in debug mode! *
* The debug console output can contain *

View file

@ -28,6 +28,7 @@ public class AppExtensionManager {
private final List<ModuleLayer> leafModuleLayers = new ArrayList<>();
private final List<Path> extensionBaseDirectories = new ArrayList<>();
private ModuleLayer baseLayer = ModuleLayer.boot();
@Getter
private ModuleLayer extendedLayer;
@ -51,11 +52,20 @@ public class AppExtensionManager {
XPipeServiceProviders.load(INSTANCE.extendedLayer);
MessageExchangeImpls.loadAll();
} catch (Throwable t) {
throw new ExtensionException("Service provider initialization failed. Is the installation data corrupt?", t);
throw new ExtensionException(
"Service provider initialization failed. Is the installation data corrupt?", t);
}
}
}
public static void reset() {
INSTANCE = null;
}
public static AppExtensionManager getInstance() {
return INSTANCE;
}
private void loadBaseExtension() {
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
if (baseModule.isEmpty()) {
@ -94,14 +104,6 @@ public class AppExtensionManager {
extensionBaseDirectories.add(productionRoot);
}
public static void reset() {
INSTANCE = null;
}
public static AppExtensionManager getInstance() {
return INSTANCE;
}
public Set<Module> getContentModules() {
return Stream.concat(
Stream.of(ModuleLayer.boot().findModule("io.xpipe.app").orElseThrow()),
@ -111,7 +113,8 @@ public class AppExtensionManager {
private void loadAllExtensions() {
for (var ext : List.of("jdbc", "proc", "uacc")) {
var extension = findAndParseExtension(ext,baseLayer).orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
var extension = findAndParseExtension(ext, baseLayer)
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
loadedExtensions.add(extension);
leafModuleLayers.add(extension.getModule().getLayer());
}

View file

@ -107,6 +107,7 @@ public class AppFileWatcher {
private class WatchedDirectory {
private final BiConsumer<Path, WatchEvent.Kind<Path>> listener;
@Getter
private final Path baseDir;
@ -114,9 +115,7 @@ public class AppFileWatcher {
this.baseDir = dir;
this.listener = listener;
createRecursiveWatchers(dir);
TrackEvent.withTrace("Added watched directory")
.tag("location", dir)
.handle();
TrackEvent.withTrace("Added watched directory").tag("location", dir).handle();
}
private void createRecursiveWatchers(Path dir) {
@ -184,6 +183,5 @@ public class AppFileWatcher {
.handle();
listener.accept(file, ev.kind());
}
}
}

View file

@ -54,7 +54,8 @@ public class AppFont {
try (var in = Files.newInputStream(file)) {
Font.loadFont(in, OsType.getLocal() == OsType.LINUX ? 11 : 12);
} catch (Throwable t) {
// Font loading can fail in rare cases. This is however not important, so we can just ignore it
// Font loading can fail in rare cases. This is however not important, so we can just ignore
// it
}
return FileVisitResult.CONTINUE;
}

View file

@ -37,10 +37,10 @@ import java.util.regex.Pattern;
public class AppI18n {
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
private static final AppI18n INSTANCE = new AppI18n();
private Map<String, String> translations;
private Map<String, String> markdownDocumentations;
private PrettyTime prettyTime;
private static final AppI18n INSTANCE = new AppI18n();
public static void init() {
var i = INSTANCE;
@ -98,8 +98,11 @@ public class AppI18n {
return "null";
}
return getInstance().prettyTime.formatDuration(
getInstance().prettyTime.approximateDuration(Instant.now().plus(duration.getValue())));
return getInstance()
.prettyTime
.formatDuration(getInstance()
.prettyTime
.approximateDuration(Instant.now().plus(duration.getValue())));
},
duration);
}
@ -136,20 +139,6 @@ public class AppI18n {
return s;
}
private void clear() {
translations.clear();
prettyTime = null;
}
@SuppressWarnings("removal")
public static class CallingClass extends SecurityManager {
public static final CallingClass INSTANCE = new CallingClass();
public Class<?>[] getCallingClasses() {
return getClassContext();
}
}
@SneakyThrows
private static String getCallerModuleName() {
var callers = CallingClass.INSTANCE.getCallingClasses();
@ -171,6 +160,11 @@ public class AppI18n {
return "";
}
private void clear() {
translations.clear();
prettyTime = null;
}
public String getKey(String s) {
var key = s;
if (!s.contains(".")) {
@ -220,7 +214,8 @@ public class AppI18n {
public String getMarkdownDocumentation(String name) {
if (!markdownDocumentations.containsKey(name)) {
TrackEvent.withWarn("Markdown documentation for key " + name + " not found").handle();
TrackEvent.withWarn("Markdown documentation for key " + name + " not found")
.handle();
}
return markdownDocumentations.getOrDefault(name, "");
@ -315,4 +310,13 @@ public class AppI18n {
? AppPrefs.get().language().getValue().getLocale()
: SupportedLocale.ENGLISH.getLocale());
}
@SuppressWarnings("removal")
public static class CallingClass extends SecurityManager {
public static final CallingClass INSTANCE = new CallingClass();
public Class<?>[] getCallingClasses() {
return getClassContext();
}
}
}

View file

@ -21,16 +21,20 @@ import java.util.List;
public class AppLayoutModel {
@Data
@Builder
@Jacksonized
public static class SavedState {
double sidebarWidth;
double browserConnectionsWidth;
}
private static AppLayoutModel INSTANCE;
@Getter
private final SavedState savedState;
@Getter
private final List<Entry> entries;
private final Property<Entry> selected;
private final ObservableValue<Entry> selectedWrapper;
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
this.selectedWrapper = PlatformThread.sync(selected);
}
public static AppLayoutModel get() {
return INSTANCE;
@ -46,20 +50,6 @@ public class AppLayoutModel {
INSTANCE = null;
}
@Getter
private final SavedState savedState;
@Getter
private final List<Entry> entries;
private final Property<Entry> selected;
private final ObservableValue<Entry> selectedWrapper;
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
this.selectedWrapper = PlatformThread.sync(selected);
}
public Property<Entry> getSelectedInternal() {
return selected;
}
@ -86,17 +76,14 @@ public class AppLayoutModel {
private List<Entry> createEntryList() {
var l = new ArrayList<>(List.of(
new Entry(
AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
new Entry(
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp())));
new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp())));
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
// StorageLayoutComp()),
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) {
l.add(new Entry(
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
l.add(new Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
}
l.add(new Entry(
@ -107,5 +94,14 @@ public class AppLayoutModel {
return l;
}
@Data
@Builder
@Jacksonized
public static class SavedState {
double sidebarWidth;
double browserConnectionsWidth;
}
public record Entry(ObservableValue<String> name, String icon, Comp<?> comp) {}
}

View file

@ -44,10 +44,13 @@ public class AppLogs {
DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(ZoneId.systemDefault());
private static AppLogs INSTANCE;
@Getter
private final PrintStream originalSysOut;
@Getter
private final PrintStream originalSysErr;
private final Path logDir;
@Getter
@ -61,7 +64,8 @@ public class AppLogs {
private final PrintStream outFileStream;
public AppLogs(Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel, PrintStream outFileStream) {
public AppLogs(
Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel, PrintStream outFileStream) {
this.logDir = logDir;
this.writeToSysout = writeToSysout;
this.writeToFile = writeToFile;
@ -171,6 +175,15 @@ public class AppLogs {
return INSTANCE;
}
private static String determineLogLevel() {
if (System.getProperty(LOG_LEVEL_PROP) != null) {
String p = System.getProperty(LOG_LEVEL_PROP);
return LOG_LEVELS.contains(p) ? p : "trace";
}
return DEFAULT_LOG_LEVEL;
}
private void close() {
if (outFileStream != null) {
outFileStream.close();
@ -197,11 +210,7 @@ public class AppLogs {
return;
}
TrackEvent.builder()
.type("info")
.message(line)
.build()
.handle();
TrackEvent.builder().type("info").message(line).build().handle();
baos.reset();
} else {
baos.write(b);
@ -231,15 +240,6 @@ public class AppLogs {
}));
}
private static String determineLogLevel() {
if (System.getProperty(LOG_LEVEL_PROP) != null) {
String p = System.getProperty(LOG_LEVEL_PROP);
return LOG_LEVELS.contains(p) ? p : "trace";
}
return DEFAULT_LOG_LEVEL;
}
public void logException(String description, Throwable e) {
var deob = Deobfuscator.deobfuscateToString(e);
var event = TrackEvent.builder()

View file

@ -19,15 +19,20 @@ public class AppProperties {
private static final String EXTENSION_PATHS_PROP = "io.xpipe.app.extensions";
private static AppProperties INSTANCE;
boolean fullVersion;
@Getter
String version;
@Getter
String build;
UUID buildUuid;
String sentryUrl;
String arch;
@Getter
boolean image;
boolean staging;
boolean useVirtualThreads;
boolean debugThreads;
@ -102,8 +107,7 @@ public class AppProperties {
}
public boolean isDevelopmentEnvironment() {
return !AppProperties.get().isImage()
&& AppProperties.get().isDeveloperMode();
return !AppProperties.get().isImage() && AppProperties.get().isDeveloperMode();
}
public boolean isDeveloperMode() {
@ -113,5 +117,4 @@ public class AppProperties {
return AppPrefs.get().developerMode().getValue();
}
}

View file

@ -57,7 +57,10 @@ public class AppSocketServer {
.handle();
} catch (Exception ex) {
// Not terminal!
ErrorEvent.fromThrowable(ex).description("Unable to start local socket server on port " + port).build().handle();
ErrorEvent.fromThrowable(ex)
.description("Unable to start local socket server on port " + port)
.build()
.handle();
}
}
@ -246,9 +249,7 @@ public class AppSocketServer {
}
}
TrackEvent.builder()
.type("trace")
.message("Socket connection #" + id + " finished unsuccessfully");
TrackEvent.builder().type("trace").message("Socket connection #" + id + " finished unsuccessfully");
}
private void performExchangesAsync(Socket clientSocket) {

View file

@ -15,8 +15,9 @@ public class AppState {
boolean initialLaunch;
@NonFinal
@Setter
@Setter
String userName;
@NonFinal
@Setter
String userEmail;

View file

@ -55,10 +55,12 @@ public class AppStyle {
try {
var bytes = Files.readAllBytes(file);
if (file.getFileName().toString().endsWith(".bss")) {
var s = "data:application/octet-stream;base64," + Base64.getEncoder().encodeToString(bytes);
var s = "data:application/octet-stream;base64,"
+ Base64.getEncoder().encodeToString(bytes);
STYLESHEET_CONTENTS.put(file, s);
} else if (file.getFileName().toString().endsWith(".css")) {
var s = "data:text/css;base64," + Base64.getEncoder().encodeToString(bytes);
var s = "data:text/css;base64,"
+ Base64.getEncoder().encodeToString(bytes);
STYLESHEET_CONTENTS.put(file, s);
}
} catch (IOException ex) {

View file

@ -79,11 +79,13 @@ public class AppTheme {
Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> {
Platform.runLater(() -> {
if (t1 == ColorScheme.DARK && !AppPrefs.get().theme.getValue().isDark()) {
if (t1 == ColorScheme.DARK
&& !AppPrefs.get().theme.getValue().isDark()) {
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
}
if (t1 != ColorScheme.DARK && AppPrefs.get().theme.getValue().isDark()) {
if (t1 != ColorScheme.DARK
&& AppPrefs.get().theme.getValue().isDark()) {
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
}
});
@ -204,6 +206,10 @@ public class AppTheme {
// Also include your custom theme here
public static final List<Theme> ALL =
List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA);
protected final String id;
@Getter
protected final String cssId;
protected final atlantafx.base.theme.Theme theme;
static Theme getDefaultLightTheme() {
return switch (OsType.getLocal()) {
@ -221,13 +227,6 @@ public class AppTheme {
};
}
protected final String id;
@Getter
protected final String cssId;
protected final atlantafx.base.theme.Theme theme;
public boolean isDark() {
return theme.isDarkMode();
}

View file

@ -14,6 +14,7 @@ public class AppTray {
private static AppTray INSTANCE;
private final AppTrayIcon icon;
@Getter
private final ErrorHandler errorHandler;

View file

@ -20,15 +20,17 @@ public class AppTrayIcon {
tray = SystemTray.getSystemTray();
var image = switch (OsType.getLocal()) {
case OsType.Windows windows -> "img/logo/logo_16x16.png";
case OsType.Linux linux -> "img/logo/logo_24x24.png";
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
};
var image =
switch (OsType.getLocal()) {
case OsType.Windows windows -> "img/logo/logo_16x16.png";
case OsType.Linux linux -> "img/logo/logo_24x24.png";
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
};
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow();
PopupMenu popupMenu = new PopupMenu();
this.trayIcon = new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu);
this.trayIcon =
new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu);
this.trayIcon.setToolTip("XPipe");
this.trayIcon.setImageAutoSize(true);
@ -58,6 +60,19 @@ public class AppTrayIcon {
});
}
private static Image loadImageFromURL(URL iconImagePath) {
try {
return ImageIO.read(iconImagePath);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE);
}
}
public static boolean isSupported() {
return Desktop.isDesktopSupported() && SystemTray.isSupported();
}
public final TrayIcon getAwtTrayIcon() {
return trayIcon;
}
@ -65,17 +80,7 @@ public class AppTrayIcon {
private void ensureSystemTraySupported() {
if (!SystemTray.isSupported()) {
throw new UnsupportedOperationException(
"SystemTray icons are not "
+ "supported by the current desktop environment.");
}
}
private static Image loadImageFromURL(URL iconImagePath) {
try {
return ImageIO.read(iconImagePath);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE);
"SystemTray icons are not " + "supported by the current desktop environment.");
}
}
@ -129,11 +134,9 @@ public class AppTrayIcon {
public void showInfoMessage(String title, String message) {
if (OsType.getLocal().equals(OsType.MACOS)) {
showMacAlert(title, message,"Information");
showMacAlert(title, message, "Information");
} else {
EventQueue.invokeLater(() ->
this.trayIcon.displayMessage(
title, message, TrayIcon.MessageType.INFO));
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.INFO));
}
}
@ -143,11 +146,9 @@ public class AppTrayIcon {
public void showWarningMessage(String title, String message) {
if (OsType.getLocal().equals(OsType.MACOS)) {
showMacAlert(title, message,"Warning");
showMacAlert(title, message, "Warning");
} else {
EventQueue.invokeLater(() ->
this.trayIcon.displayMessage(
title, message, TrayIcon.MessageType.WARNING));
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.WARNING));
}
}
@ -157,11 +158,9 @@ public class AppTrayIcon {
public void showErrorMessage(String title, String message) {
if (OsType.getLocal().equals(OsType.MACOS)) {
showMacAlert(title, message,"Error");
showMacAlert(title, message, "Error");
} else {
EventQueue.invokeLater(() ->
this.trayIcon.displayMessage(
title, message, TrayIcon.MessageType.ERROR));
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.ERROR));
}
}
@ -171,11 +170,9 @@ public class AppTrayIcon {
public void showMessage(String title, String message) {
if (OsType.getLocal().equals(OsType.MACOS)) {
showMacAlert(title, message,"Message");
showMacAlert(title, message, "Message");
} else {
EventQueue.invokeLater(() ->
this.trayIcon.displayMessage(
title, message, TrayIcon.MessageType.NONE));
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.NONE));
}
}
@ -183,26 +180,14 @@ public class AppTrayIcon {
this.showMessage(null, message);
}
public static boolean isSupported() {
return Desktop.isDesktopSupported() && SystemTray.isSupported();
}
private void showMacAlert(String subTitle, String message, String title) {
String execute = String.format(
"display notification \"%s\""
+ " with title \"%s\""
+ " subtitle \"%s\"",
message != null ? message : "",
title != null ? title : "",
subTitle != null ? subTitle : ""
);
"display notification \"%s\"" + " with title \"%s\"" + " subtitle \"%s\"",
message != null ? message : "", title != null ? title : "", subTitle != null ? subTitle : "");
try {
Runtime.getRuntime()
.exec(new String[] { "osascript", "-e", execute });
Runtime.getRuntime().exec(new String[] {"osascript", "-e", execute});
} catch (IOException e) {
throw new UnsupportedOperationException(
"Cannot run osascript with given parameters.");
throw new UnsupportedOperationException("Cannot run osascript with given parameters.");
}
}
}

View file

@ -109,8 +109,7 @@ public class AppWindowHelper {
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2);
}
public static void showAlert(
Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
public static void showAlert(Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
ThreadHelper.runAsync(() -> {
var r = showBlockingAlert(c);
if (bt != null) {
@ -137,7 +136,8 @@ public class AppWindowHelper {
.orElse(false);
}
public static boolean showConfirmationAlert(ObservableValue<String> title, ObservableValue<String> header, ObservableValue<String> content) {
public static boolean showConfirmationAlert(
ObservableValue<String> title, ObservableValue<String> header, ObservableValue<String> content) {
return AppWindowHelper.showBlockingAlert(alert -> {
alert.titleProperty().bind(title);
alert.headerTextProperty().bind(header);
@ -273,7 +273,11 @@ public class AppWindowHelper {
}
var allScreenBounds = computeWindowScreenBounds(stage);
if (!areNumbersValid(allScreenBounds.getMinX(), allScreenBounds.getMinY(), allScreenBounds.getMaxX(), allScreenBounds.getMaxY())) {
if (!areNumbersValid(
allScreenBounds.getMinX(),
allScreenBounds.getMinY(),
allScreenBounds.getMaxX(),
allScreenBounds.getMaxY())) {
return Optional.empty();
}
@ -324,43 +328,46 @@ public class AppWindowHelper {
private static List<Screen> getWindowScreens(Stage stage) {
if (!areNumbersValid(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())) {
return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage ? getWindowScreens(ownerStage) : List.of(Screen.getPrimary());
return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage
? getWindowScreens(ownerStage)
: List.of(Screen.getPrimary());
}
return Screen.getScreensForRectangle(new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()));
return Screen.getScreensForRectangle(
new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()));
}
private static Rectangle2D computeWindowScreenBounds(Stage stage) {
double minX = Double.POSITIVE_INFINITY ;
double minY = Double.POSITIVE_INFINITY ;
double maxX = Double.NEGATIVE_INFINITY ;
double maxY = Double.NEGATIVE_INFINITY ;
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for (Screen screen : getWindowScreens(stage)) {
Rectangle2D screenBounds = screen.getBounds();
if (screenBounds.getMinX() < minX) {
minX = screenBounds.getMinX();
}
if (screenBounds.getMinY() < minY) {
minY = screenBounds.getMinY() ;
minY = screenBounds.getMinY();
}
if (screenBounds.getMaxX() > maxX) {
maxX = screenBounds.getMaxX();
}
if (screenBounds.getMaxY() > maxY) {
maxY = screenBounds.getMaxY() ;
maxY = screenBounds.getMaxY();
}
}
// Taskbar adjustment
maxY -= 50;
var w = maxX-minX;
var h = maxY-minY;
var w = maxX - minX;
var h = maxY - minY;
// This should not happen but on weird Linux systems nothing is impossible
if (w < 0 || h < 0) {
return new Rectangle2D(0,0,800, 600);
return new Rectangle2D(0, 0, 800, 600);
}
return new Rectangle2D(minX, minY, w, h);
}

View file

@ -17,54 +17,6 @@ import java.util.Optional;
public class AppAvCheck {
@Getter
public enum AvType {
BITDEFENDER("Bitdefender") {
@Override
public String getDescription() {
return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Bitdefender", "InstallDir");
}
},
MALWAREBYTES("Malwarebytes") {
@Override
public String getDescription() {
return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Malwarebytes", "id");
}
},
MCAFEE("McAfee") {
@Override
public String getDescription() {
return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\McAfee", "mi");
}
};
private final String name;
AvType(String name) {
this.name = name;
}
public abstract String getDescription();
public abstract boolean isActive();
}
private static Optional<AvType> detect() {
for (AvType value : AvType.values()) {
if (value.isActive()) {
@ -93,21 +45,75 @@ public class AppAvCheck {
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
alert.setAlertType(Alert.AlertType.NONE);
AppResources.with(
AppResources.XPIPE_MODULE,
"misc/antivirus.md",
file -> {
var markdown = new MarkdownComp(Files.readString(file), s -> {
AppResources.with(AppResources.XPIPE_MODULE, "misc/antivirus.md", file -> {
var markdown = new MarkdownComp(Files.readString(file), s -> {
var t = found.get();
return s.formatted(t.getName(), t.getName(), t.getDescription(), AppProperties.get().getVersion(), AppProperties.get().getVersion(), t.getName());
}).prefWidth(550).prefHeight(600).createRegion();
alert.getDialogPane().setContent(markdown);
alert.getDialogPane().setPadding(new Insets(15));
});
return s.formatted(
t.getName(),
t.getName(),
t.getDescription(),
AppProperties.get().getVersion(),
AppProperties.get().getVersion(),
t.getName());
})
.prefWidth(550)
.prefHeight(600)
.createRegion();
alert.getDialogPane().setContent(markdown);
alert.getDialogPane().setPadding(new Insets(15));
});
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
});
a.filter(b -> b.getButtonData().isDefaultButton())
.ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1));
}
@Getter
public enum AvType {
BITDEFENDER("Bitdefender") {
@Override
public String getDescription() {
return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir");
}
},
MALWAREBYTES("Malwarebytes") {
@Override
public String getDescription() {
return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Malwarebytes", "id");
}
},
MCAFEE("McAfee") {
@Override
public String getDescription() {
return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors.";
}
@Override
public boolean isActive() {
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\McAfee", "mi");
}
};
private final String name;
AvType(String name) {
this.name = name;
}
public abstract String getDescription();
public abstract boolean isActive();
}
}

View file

@ -8,7 +8,8 @@ import java.util.concurrent.TimeUnit;
public class AppCertutilCheck {
private static boolean getResult() {
var fc = new ProcessBuilder(System.getenv("WINDIR") + "\\Windows32\\certutil").redirectError(ProcessBuilder.Redirect.DISCARD);
var fc = new ProcessBuilder(System.getenv("WINDIR") + "\\Windows32\\certutil")
.redirectError(ProcessBuilder.Redirect.DISCARD);
try {
var proc = fc.start();
var out = new String(proc.getInputStream().readAllBytes());

View file

@ -12,19 +12,25 @@ public class AppShellCheck {
public static void check() {
var err = selfTestErrorCheck();
if (err.isPresent()) {
var msg = """
var msg =
"""
Shell self-test failed for %s:
%s
This indicates that something is seriously wrong and certain shell functionality will not work as expected.
The most likely causes are:
- On Windows, an AntiVirus program might block required programs and commands
- The system shell is restricted or blocked
- The operating system is not supported
You can reach out to us if you want to properly diagnose the cause individually and hopefully fix it.
""".formatted(ProcessControlProvider.get().getEffectiveLocalDialect().getDisplayName(), err.get());
"""
.formatted(
ProcessControlProvider.get()
.getEffectiveLocalDialect()
.getDisplayName(),
err.get());
ErrorEvent.fromThrowable(new IllegalStateException(msg)).handle();
}
}

View file

@ -18,8 +18,8 @@ public class AppTempCheck {
}
if (dir == null || !Files.exists(dir) || !Files.isDirectory(dir)) {
ErrorEvent.fromThrowable(
new IOException("Specified temporary directory " + tmpdir + ", set via the environment variable %TEMP% is invalid."))
ErrorEvent.fromThrowable(new IOException("Specified temporary directory " + tmpdir
+ ", set via the environment variable %TEMP% is invalid."))
.term()
.handle();
}

View file

@ -11,7 +11,10 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.*;
import io.xpipe.app.util.FileBridge;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.UnlockAlert;
import io.xpipe.core.util.JacksonMapper;
public class BaseMode extends OperationMode {

View file

@ -16,6 +16,16 @@ public class GuiMode extends PlatformMode {
return "gui";
}
@Override
public void onSwitchFrom() {
PlatformThread.runLaterIfNeededBlocking(() -> {
TrackEvent.info("Closing windows");
Stage.getWindows().stream().toList().forEach(w -> {
w.hide();
});
});
}
@Override
public void onSwitchTo() throws Throwable {
super.onSwitchTo();
@ -37,14 +47,4 @@ public class GuiMode extends PlatformMode {
UpdateChangelogAlert.showIfNeeded();
}
@Override
public void onSwitchFrom() {
PlatformThread.runLaterIfNeededBlocking(() -> {
TrackEvent.info("Closing windows");
Stage.getWindows().stream().toList().forEach(w -> {
w.hide();
});
});
}
}

View file

@ -36,12 +36,16 @@ public abstract class OperationMode {
public static final OperationMode GUI = new GuiMode();
private static final Pattern PROPERTY_PATTERN = Pattern.compile("^-[DP](.+)=(.+)$");
private static final List<OperationMode> ALL = List.of(BACKGROUND, TRAY, GUI);
@Getter
private static boolean inStartup;
@Getter
private static boolean inShutdown;
@Getter
private static boolean inShutdownHook;
private static OperationMode CURRENT = null;
public static OperationMode map(XPipeDaemonMode mode) {
@ -137,8 +141,8 @@ public abstract class OperationMode {
});
// Do it this way to prevent IDE inspections from complaining
var c = Class.forName(ModuleLayer.boot().findModule("java.desktop").orElseThrow(),
"com.apple.eawt.Application");
var c = Class.forName(
ModuleLayer.boot().findModule("java.desktop").orElseThrow(), "com.apple.eawt.Application");
var m = c.getDeclaredMethod("addAppEventListener", SystemEventListener.class);
m.invoke(c.getMethod("getApplication").invoke(null), new AppReopenedListener() {
@Override
@ -170,19 +174,23 @@ public abstract class OperationMode {
public static void switchToAsync(OperationMode newMode) {
ThreadHelper.createPlatformThread("mode switcher", false, () -> {
switchToSyncIfPossible(newMode);
}).start();
switchToSyncIfPossible(newMode);
})
.start();
}
public static void switchToSyncOrThrow(OperationMode newMode) throws Throwable {
TrackEvent.info("Attempting to switch mode to " + newMode.getId());
if (!newMode.isSupported()) {
throw PlatformState.getLastError() != null ? PlatformState.getLastError() : new IllegalStateException("Unsupported operation mode: " + newMode.getId());
throw PlatformState.getLastError() != null
? PlatformState.getLastError()
: new IllegalStateException("Unsupported operation mode: " + newMode.getId());
}
set(newMode);
}
public static boolean switchToSyncIfPossible(OperationMode newMode) {
TrackEvent.info("Attempting to switch mode to " + newMode.getId());
@ -202,7 +210,6 @@ public abstract class OperationMode {
return true;
}
public static void switchUp(OperationMode newMode) {
if (newMode == BACKGROUND) {
return;
@ -231,7 +238,8 @@ public abstract class OperationMode {
public static void restart() {
OperationMode.executeAfterShutdown(() -> {
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, "");
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(
XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, "");
LocalShell.getShell().executeSimpleCommand(exec);
});
}
@ -312,20 +320,20 @@ public abstract class OperationMode {
OperationMode.halt(hasError ? 1 : 0);
}
// public static synchronized void reload() {
// ThreadHelper.create("reloader", false, () -> {
// try {
// switchTo(BACKGROUND);
// CURRENT.finalTeardown();
// CURRENT.onSwitchTo();
// switchTo(GUI);
// } catch (Throwable t) {
// ErrorEvent.fromThrowable(t).build().handle();
// OperationMode.halt(1);
// }
// })
// .start();
// }
// public static synchronized void reload() {
// ThreadHelper.create("reloader", false, () -> {
// try {
// switchTo(BACKGROUND);
// CURRENT.finalTeardown();
// CURRENT.onSwitchTo();
// switchTo(GUI);
// } catch (Throwable t) {
// ErrorEvent.fromThrowable(t).build().handle();
// OperationMode.halt(1);
// }
// })
// .start();
// }
private static synchronized void set(OperationMode newMode) {
if (CURRENT == null && newMode == null) {

View file

@ -11,12 +11,10 @@ public class TrayMode extends PlatformMode {
@Override
public boolean isSupported() {
return !OsType.getLocal().equals(OsType.MACOS) && super.isSupported() && Desktop.isDesktopSupported() && SystemTray.isSupported();
}
@Override
public String getId() {
return "tray";
return !OsType.getLocal().equals(OsType.MACOS)
&& super.isSupported()
&& Desktop.isDesktopSupported()
&& SystemTray.isSupported();
}
@Override
@ -33,6 +31,11 @@ public class TrayMode extends PlatformMode {
});
}
@Override
public String getId() {
return "tray";
}
@Override
public void onSwitchFrom() {
if (AppTray.get() != null) {

View file

@ -11,7 +11,9 @@ public class AskpassExchangeImpl extends AskpassExchange
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
var found = msg.getSecretId() != null ? SecretManager.getProgress(msg.getRequest(), msg.getSecretId()) : SecretManager.getProgress(msg.getRequest());
var found = msg.getSecretId() != null
? SecretManager.getProgress(msg.getRequest(), msg.getSecretId())
: SecretManager.getProgress(msg.getRequest());
if (found.isEmpty()) {
return Response.builder().build();
}
@ -21,6 +23,8 @@ public class AskpassExchangeImpl extends AskpassExchange
var p = found.get();
var secret = p.process(msg.getPrompt());
return Response.builder().value(secret != null ? secret.inPlace() : null).build();
return Response.builder()
.value(secret != null ? secret.inPlace() : null)
.build();
}
}

View file

@ -16,9 +16,8 @@ public class LaunchExchangeImpl extends LaunchExchange
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var store = getStoreEntryById(msg.getId(), false);
if (store.getStore() instanceof LaunchableStore s) {
var command = s.prepareLaunchCommand().prepareTerminalOpen(
TerminalInitScriptConfig.ofName(store.getName()),
null);
var command = s.prepareLaunchCommand()
.prepareTerminalOpen(TerminalInitScriptConfig.ofName(store.getName()), null);
return Response.builder().command(split(command)).build();
}
@ -28,9 +27,8 @@ public class LaunchExchangeImpl extends LaunchExchange
private List<String> split(String command) {
var split = Arrays.stream(command.split(" ", 3)).collect(Collectors.toList());
var s = split.get(2);
if ((s.startsWith("\"") && s.endsWith("\""))
|| (s.startsWith("'") && s.endsWith("'"))) {
split.set(2,s.substring(1, s.length() - 1));
if ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))) {
split.set(2, s.substring(1, s.length() - 1));
}
return split;
}

View file

@ -34,8 +34,8 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
String.format("Store %s is disabled", store.get().getName()));
}
if (!store.get().getValidity().isUsable() && !acceptUnusable) {
throw new ClientException(
String.format("Store %s is not completely configured", store.get().getName()));
throw new ClientException(String.format(
"Store %s is not completely configured", store.get().getName()));
}
return store.get();
}

View file

@ -3,7 +3,6 @@ package io.xpipe.app.exchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.ServerException;
import io.xpipe.beacon.exchange.TerminalLaunchExchange;
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange

View file

@ -19,8 +19,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
var all = DataStoreProviders.getAll();
var map = Arrays.stream(categories)
.collect(Collectors.toMap(category -> getName(category), category -> all.stream()
.filter(dataStoreProvider ->
category.equals(dataStoreProvider.getCreationCategory()))
.filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()))
.map(p -> ProviderEntry.builder()
.id(p.getId())
.description(p.getDisplayDescription())

View file

@ -26,6 +26,107 @@ public interface ActionProvider {
}
}
default void init() {}
default String getId() {
return null;
}
default boolean isActive() {
return true;
}
default String getProFeatureId() {
return null;
}
default LauncherCallSite getLauncherCallSite() {
return null;
}
default DataStoreCallSite<?> getDataStoreCallSite() {
return null;
}
default DefaultDataStoreCallSite<?> getDefaultDataStoreCallSite() {
return null;
}
interface Action {
boolean requiresJavaFXPlatform();
void execute() throws Exception;
}
interface LauncherCallSite {
String getId();
Action createAction(URI uri) throws Exception;
}
interface XPipeLauncherCallSite extends LauncherCallSite {
String getId();
default Action createAction(URI uri) throws Exception {
var args = new ArrayList<>(Arrays.asList(uri.getPath().substring(1).split("/")));
args.addFirst(uri.getHost());
return createAction(args);
}
Action createAction(List<String> args) throws Exception;
}
interface DefaultDataStoreCallSite<T extends DataStore> {
Action createAction(DataStoreEntryRef<T> store);
Class<T> getApplicableClass();
default boolean isApplicable(DataStoreEntryRef<T> o) {
return true;
}
}
interface DataStoreCallSite<T extends DataStore> {
default boolean isSystemAction() {
return false;
}
default boolean canLinkTo() {
return false;
}
Action createAction(DataStoreEntryRef<T> store);
Class<T> getApplicableClass();
default boolean isMajor(DataStoreEntryRef<T> o) {
return false;
}
default boolean isApplicable(DataStoreEntryRef<T> o) {
return true;
}
ObservableValue<String> getName(DataStoreEntryRef<T> store);
String getIcon(DataStoreEntryRef<T> store);
default ActiveType activeType() {
return ActiveType.ONLY_SHOW_IF_ENABLED;
}
enum ActiveType {
ONLY_SHOW_IF_ENABLED,
ALWAYS_SHOW,
ALWAYS_ENABLE
}
}
class Loader implements ModuleLayerLoader {
@Override
@ -53,106 +154,4 @@ public interface ActionProvider {
return false;
}
}
interface Action {
boolean requiresJavaFXPlatform();
void execute() throws Exception;
}
default void init() {
}
default String getId() {
return null;
}
default boolean isActive() {
return true;
}
default String getProFeatureId() {
return null;
}
interface LauncherCallSite {
String getId();
Action createAction(URI uri) throws Exception;
}
interface XPipeLauncherCallSite extends LauncherCallSite {
String getId();
default Action createAction(URI uri) throws Exception {
var args = new ArrayList<>(Arrays.asList(uri.getPath().substring(1).split("/")));
args.addFirst(uri.getHost());
return createAction(args);
}
Action createAction(List<String> args) throws Exception;
}
default LauncherCallSite getLauncherCallSite() {
return null;
}
default DataStoreCallSite<?> getDataStoreCallSite() {
return null;
}
default DefaultDataStoreCallSite<?> getDefaultDataStoreCallSite() {
return null;
}
interface DefaultDataStoreCallSite<T extends DataStore> {
Action createAction(DataStoreEntryRef<T> store);
Class<T> getApplicableClass();
default boolean isApplicable(DataStoreEntryRef<T> o) {
return true;
}
}
interface DataStoreCallSite<T extends DataStore> {
enum ActiveType {
ONLY_SHOW_IF_ENABLED,
ALWAYS_SHOW,
ALWAYS_ENABLE
}
default boolean isSystemAction() {
return false;
}
default boolean canLinkTo() {
return false;
}
Action createAction(DataStoreEntryRef<T> store);
Class<T> getApplicableClass();
default boolean isMajor(DataStoreEntryRef<T> o) {
return false;
}
default boolean isApplicable(DataStoreEntryRef<T> o) {
return true;
}
ObservableValue<String> getName(DataStoreEntryRef<T> store);
String getIcon(DataStoreEntryRef<T> store);
default ActiveType activeType() {
return ActiveType.ONLY_SHOW_IF_ENABLED;
}
}
}

View file

@ -123,8 +123,7 @@ public interface DataStoreProvider {
return true;
}
default void postInit(){
}
default void postInit() {}
default void storageInit() {}

View file

@ -62,7 +62,9 @@ public class DataStoreProviders {
String.join("_", split),
String.join("-", split),
split.stream()
.map(s -> s.equals(split.getFirst()) ? s : s.substring(0, 1).toUpperCase() + s.substring(1))
.map(s -> s.equals(split.getFirst())
? s
: s.substring(0, 1).toUpperCase() + s.substring(1))
.collect(Collectors.joining()));
}

View file

@ -10,6 +10,22 @@ public abstract class PrefsProvider {
private static List<PrefsProvider> ALL;
public static List<PrefsProvider> getAll() {
return ALL;
}
@SuppressWarnings("unchecked")
public static <T extends PrefsProvider> T get(Class<T> c) {
return (T) ALL.stream()
.filter(prefsProvider -> prefsProvider.getClass().equals(c))
.findAny()
.orElseThrow();
}
public abstract void addPrefs(PrefsHandler handler);
public abstract void initDefaultValues();
public static class Loader implements ModuleLayerLoader {
@Override
@ -29,20 +45,4 @@ public abstract class PrefsProvider {
return false;
}
}
public static List<PrefsProvider> getAll() {
return ALL;
}
@SuppressWarnings("unchecked")
public static <T extends PrefsProvider> T get(Class<T> c) {
return (T) ALL.stream()
.filter(prefsProvider -> prefsProvider.getClass().equals(c))
.findAny()
.orElseThrow();
}
public abstract void addPrefs(PrefsHandler handler);
public abstract void initDefaultValues();
}

View file

@ -14,6 +14,20 @@ import java.util.stream.Collectors;
public abstract class ScanProvider {
private static List<ScanProvider> ALL;
public static List<ScanProvider> getAll() {
return ALL;
}
public ScanOperation create(DataStore store) {
return null;
}
public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception {
return null;
}
@Value
public static class ScanOperation {
String nameKey;
@ -22,8 +36,6 @@ public abstract class ScanProvider {
FailableRunnable<Exception> scanner;
}
private static List<ScanProvider> ALL;
public static class Loader implements ModuleLayerLoader {
@Override
@ -45,16 +57,4 @@ public abstract class ScanProvider {
return false;
}
}
public static List<ScanProvider> getAll() {
return ALL;
}
public ScanOperation create(DataStore store) {
return null;
}
public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception {
return null;
}
}

View file

@ -10,11 +10,10 @@ import java.util.function.Supplier;
public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S> {
private static ContextMenu currentContextMenu;
private final Predicate<MouseEvent> show;
private final Supplier<ContextMenu> contextMenu;
private static ContextMenu currentContextMenu;
public ContextMenuAugment(Predicate<MouseEvent> show, Supplier<ContextMenu> contextMenu) {
this.show = show;
this.contextMenu = contextMenu;

View file

@ -31,7 +31,6 @@ public class DraggableAugment<S extends CompStructure<?>> implements Augment<S>
circle.setTranslateY(initialTranslateY + deltaY);
lastMouseX = mouseEvent.getSceneX();
lastMouseY = mouseEvent.getSceneY();
});
circle.setOnMouseEntered(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {

View file

@ -25,15 +25,9 @@ import java.util.stream.Collectors;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
public static <T extends Translatable> ChoiceComp<T> ofTranslatable(Property<T> value, List<T> range, boolean includeNone) {
var map = range.stream().collect(Collectors.toMap(o -> o, Translatable::toTranslatedString, (v1, v2) -> v2, LinkedHashMap::new));
return new ChoiceComp<>(value, map,includeNone);
}
Property<T> value;
ObservableValue<Map<T, ObservableValue<String>>> range;
boolean includeNone;
public ChoiceComp(Property<T> value, Map<T, ObservableValue<String>> range, boolean includeNone) {
this.value = value;
this.range = new SimpleObjectProperty<>(range);
@ -46,6 +40,14 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
this.includeNone = includeNone;
}
public static <T extends Translatable> ChoiceComp<T> ofTranslatable(
Property<T> value, List<T> range, boolean includeNone) {
var map = range.stream()
.collect(
Collectors.toMap(o -> o, Translatable::toTranslatedString, (v1, v2) -> v2, LinkedHashMap::new));
return new ChoiceComp<>(value, map, includeNone);
}
@Override
public CompStructure<ComboBox<T>> createBase() {
var cb = new ComboBox<T>();

View file

@ -30,7 +30,7 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
@Override
public CompStructure<VBox> createBase() {
var list = FXCollections.observableArrayList(entries);
var list = FXCollections.observableArrayList(entries);
var cb = new ComboBox<>(list);
cb.setOnKeyPressed(event -> {
if (!cb.isShowing() && event.getCode().equals(KeyCode.ENTER)) {

Some files were not shown because too many files have changed in this diff Show more