mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Reformat and cleanup
This commit is contained in:
parent
310647a6d8
commit
68d3c4263b
299 changed files with 5222 additions and 4381 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
open module io.xpipe.app.localTest {
|
||||
requires org.junit.jupiter.api;
|
||||
requires io.xpipe.app;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,5 @@ public class Test extends LocalExtensionTest {
|
|||
public void test() {
|
||||
System.out.println("a");
|
||||
System.out.println(DataStorage.get().getStoreEntries());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -12,7 +12,6 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||
super(entry, content);
|
||||
}
|
||||
|
||||
|
||||
protected Region createContent() {
|
||||
var name = createName().createRegion();
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -15,8 +15,9 @@ public class AppState {
|
|||
boolean initialLaunch;
|
||||
|
||||
@NonFinal
|
||||
@Setter
|
||||
@Setter
|
||||
String userName;
|
||||
|
||||
@NonFinal
|
||||
@Setter
|
||||
String userEmail;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ public class AppTray {
|
|||
|
||||
private static AppTray INSTANCE;
|
||||
private final AppTrayIcon icon;
|
||||
|
||||
@Getter
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,8 +123,7 @@ public interface DataStoreProvider {
|
|||
return true;
|
||||
}
|
||||
|
||||
default void postInit(){
|
||||
}
|
||||
default void postInit() {}
|
||||
|
||||
default void storageInit() {}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue