mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
More rework
This commit is contained in:
parent
6bd105b1de
commit
6d88d3926d
172 changed files with 3068 additions and 1164 deletions
|
@ -39,6 +39,7 @@ dependencies {
|
||||||
api 'com.vladsch.flexmark:flexmark-util-visitor:0.64.8'
|
api 'com.vladsch.flexmark:flexmark-util-visitor:0.64.8'
|
||||||
|
|
||||||
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||||
|
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
||||||
api 'info.picocli:picocli:4.7.5'
|
api 'info.picocli:picocli:4.7.5'
|
||||||
api 'org.kohsuke:github-api:1.321'
|
api 'org.kohsuke:github-api:1.321'
|
||||||
api 'io.sentry:sentry:7.6.0'
|
api 'io.sentry:sentry:7.6.0'
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.browser;
|
||||||
import atlantafx.base.controls.Breadcrumbs;
|
import atlantafx.base.controls.Breadcrumbs;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -40,7 +39,7 @@ public class BrowserBreadcrumbBar extends SimpleComp {
|
||||||
|
|
||||||
var breadcrumbs = new Breadcrumbs<String>();
|
var breadcrumbs = new Breadcrumbs<String>();
|
||||||
breadcrumbs.setMinWidth(0);
|
breadcrumbs.setMinWidth(0);
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(model.getCurrentPath()), val -> {
|
PlatformThread.sync(model.getCurrentPath()).subscribe( val -> {
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
breadcrumbs.setSelectedCrumb(null);
|
breadcrumbs.setSelectedCrumb(null);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -4,22 +4,19 @@ import atlantafx.base.controls.RingProgressIndicator;
|
||||||
import atlantafx.base.controls.Spacer;
|
import atlantafx.base.controls.Spacer;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
|
||||||
import io.xpipe.app.browser.icon.FileIconManager;
|
import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
|
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||||
import io.xpipe.app.comp.base.MultiContentComp;
|
import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -52,12 +49,6 @@ public class BrowserComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
BrowserIconFileType.loadDefinitions();
|
|
||||||
BrowserIconDirectoryType.loadDefinitions();
|
|
||||||
ThreadHelper.runAsync(() -> {
|
|
||||||
FileIconManager.loadIfNecessary();
|
|
||||||
});
|
|
||||||
|
|
||||||
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
|
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
|
||||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
||||||
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||||
|
@ -133,7 +124,7 @@ public class BrowserComp extends SimpleComp {
|
||||||
private Comp<?> createTabs() {
|
private Comp<?> createTabs() {
|
||||||
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
|
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
|
||||||
Comp.of(() -> createTabPane()),
|
Comp.of(() -> createTabPane()),
|
||||||
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
|
Bindings.isNotEmpty(model.getOpenFileSystems()),
|
||||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
||||||
Bindings.createBooleanBinding(
|
Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -284,7 +275,7 @@ public class BrowserComp extends SimpleComp {
|
||||||
var id = UUID.randomUUID().toString();
|
var id = UUID.randomUUID().toString();
|
||||||
tab.setId(id);
|
tab.setId(id);
|
||||||
|
|
||||||
SimpleChangeListener.apply(tabs.skinProperty(), newValue -> {
|
tabs.skinProperty().subscribe(newValue -> {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Label l = (Label) tabs.lookup("#" + id + " .tab-label");
|
Label l = (Label) tabs.lookup("#" + id + " .tab-label");
|
||||||
|
@ -303,7 +294,7 @@ public class BrowserComp extends SimpleComp {
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
c.getStyleClass().add(color.getId());
|
c.getStyleClass().add(color.getId());
|
||||||
}
|
}
|
||||||
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
|
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
|
||||||
c.addEventHandler(
|
c.addEventHandler(
|
||||||
DragEvent.DRAG_ENTERED,
|
DragEvent.DRAG_ENTERED,
|
||||||
mouseEvent -> Platform.runLater(
|
mouseEvent -> Platform.runLater(
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class BrowserEntry {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var f : BrowserIconFileType.ALL) {
|
for (var f : BrowserIconFileType.getAll()) {
|
||||||
if (f.matches(rawFileEntry)) {
|
if (f.matches(rawFileEntry)) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public class BrowserEntry {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var f : BrowserIconDirectoryType.ALL) {
|
for (var f : BrowserIconDirectoryType.getAll()) {
|
||||||
if (f.matches(rawFileEntry)) {
|
if (f.matches(rawFileEntry)) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.HumanReadableFormat;
|
import io.xpipe.app.util.HumanReadableFormat;
|
||||||
|
@ -505,7 +504,7 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
.get();
|
.get();
|
||||||
var quickAccess = new BrowserQuickAccessButtonComp(
|
var quickAccess = new BrowserQuickAccessButtonComp(
|
||||||
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||||
.hide(BindingsHelper.persist(Bindings.createBooleanBinding(
|
.hide(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var item = getTableRow().getItem();
|
var item = getTableRow().getItem();
|
||||||
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
||||||
|
@ -514,7 +513,7 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
||||||
return notDir || isParentLink;
|
return notDir || isParentLink;
|
||||||
},
|
},
|
||||||
itemProperty())))
|
itemProperty()).not().not())
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
editing.addListener((observable, oldValue, newValue) -> {
|
editing.addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -558,7 +557,7 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
// Don't set image as that would trigger image comp update
|
// Don't set image as that would trigger image comp update
|
||||||
// and cells are emptied on each change, leading to unnecessary changes
|
// and cells are emptied on each change, leading to unnecessary changes
|
||||||
// img.set(null);
|
// img.set(null);
|
||||||
|
|
||||||
// Visibility seems to be bugged, so use opacity
|
// Visibility seems to be bugged, so use opacity
|
||||||
setOpacity(0.0);
|
setOpacity(0.0);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
@ -33,7 +33,7 @@ public final class BrowserFileListModel {
|
||||||
private final ObservableList<BrowserEntry> previousSelection = FXCollections.observableArrayList();
|
private final ObservableList<BrowserEntry> previousSelection = FXCollections.observableArrayList();
|
||||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||||
private final ObservableList<FileSystem.FileEntry> selectedRaw =
|
private final ObservableList<FileSystem.FileEntry> selectedRaw =
|
||||||
BindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
|
ListBindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
|
||||||
|
|
||||||
private final Property<BrowserEntry> draggedOverDirectory = new SimpleObjectProperty<>();
|
private final Property<BrowserEntry> draggedOverDirectory = new SimpleObjectProperty<>();
|
||||||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||||
|
|
|
@ -3,9 +3,8 @@ package io.xpipe.app.browser;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
@ -29,7 +28,7 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
||||||
var expanded = new SimpleBooleanProperty();
|
var expanded = new SimpleBooleanProperty();
|
||||||
var text = new TextFieldComp(filterString, false).createRegion();
|
var text = new TextFieldComp(filterString, false).createRegion();
|
||||||
var button = new Button();
|
var button = new Button();
|
||||||
new FancyTooltipAugment<>("app.search").augment(button);
|
new TooltipAugment<>("app.search").augment(button);
|
||||||
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (!newValue && filterString.getValue() == null) {
|
if (!newValue && filterString.getValue() == null) {
|
||||||
if (button.isFocused()) {
|
if (button.isFocused()) {
|
||||||
|
@ -47,7 +46,7 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
||||||
text.setMinWidth(0);
|
text.setMinWidth(0);
|
||||||
Styles.toggleStyleClass(text, Styles.LEFT_PILL);
|
Styles.toggleStyleClass(text, Styles.LEFT_PILL);
|
||||||
|
|
||||||
SimpleChangeListener.apply(filterString, val -> {
|
filterString.subscribe(val -> {
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
text.getStyleClass().remove(Styles.SUCCESS);
|
text.getStyleClass().remove(Styles.SUCCESS);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,8 +2,10 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
@ -15,7 +17,9 @@ public class BrowserGreetingComp extends SimpleComp {
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var r = new Label(getText());
|
var r = new Label(getText());
|
||||||
AppLayoutModel.get().getSelected().addListener((observableValue, entry, t1) -> {
|
AppLayoutModel.get().getSelected().addListener((observableValue, entry, t1) -> {
|
||||||
r.setText(getText());
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
r.setText(getText());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
AppFont.setSize(r, 7);
|
AppFont.setSize(r, 7);
|
||||||
r.getStyleClass().add(Styles.TEXT_BOLD);
|
r.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
@ -27,11 +31,11 @@ public class BrowserGreetingComp extends SimpleComp {
|
||||||
var hour = ldt.getHour();
|
var hour = ldt.getHour();
|
||||||
String text;
|
String text;
|
||||||
if (hour > 18 || hour < 5) {
|
if (hour > 18 || hour < 5) {
|
||||||
text = "Good evening";
|
text = AppI18n.get("goodEvening");
|
||||||
} else if (hour < 12) {
|
} else if (hour < 12) {
|
||||||
text = "Good morning";
|
text = AppI18n.get("goodMorning");
|
||||||
} else {
|
} else {
|
||||||
text = "Good afternoon";
|
text = AppI18n.get("goodAfternoon");
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||||
|
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||||
|
import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
|
@ -47,7 +50,7 @@ public class BrowserModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
|
ListBindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +142,13 @@ public class BrowserModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only load icons when a file system is opened
|
||||||
|
ThreadHelper.runAsync(() -> {
|
||||||
|
BrowserIconFileType.loadDefinitions();
|
||||||
|
BrowserIconDirectoryType.loadDefinitions();
|
||||||
|
FileIconManager.loadIfNecessary();
|
||||||
|
});
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
OpenFileSystemModel model;
|
OpenFileSystemModel model;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -42,7 +41,7 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var path = new SimpleStringProperty(model.getCurrentPath().get());
|
var path = new SimpleStringProperty(model.getCurrentPath().get());
|
||||||
SimpleChangeListener.apply(model.getCurrentPath(), (newValue) -> {
|
model.getCurrentPath().subscribe((newValue) -> {
|
||||||
path.set(newValue);
|
path.set(newValue);
|
||||||
});
|
});
|
||||||
path.addListener((observable, oldValue, newValue) -> {
|
path.addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -58,7 +57,7 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
.styleClass(Styles.CENTER_PILL)
|
.styleClass(Styles.CENTER_PILL)
|
||||||
.styleClass("path-text")
|
.styleClass("path-text")
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
SimpleChangeListener.apply(struc.get().focusedProperty(), val -> {
|
struc.get().focusedProperty().subscribe(val -> {
|
||||||
struc.get()
|
struc.get()
|
||||||
.pseudoClassStateChanged(
|
.pseudoClassStateChanged(
|
||||||
INVISIBLE,
|
INVISIBLE,
|
||||||
|
@ -71,7 +70,7 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SimpleChangeListener.apply(model.getInOverview(), val -> {
|
model.getInOverview().subscribe(val -> {
|
||||||
// Pseudo classes do not apply if set instantly before shown
|
// Pseudo classes do not apply if set instantly before shown
|
||||||
// If we start a new tab with a directory set, we have to set the pseudo class one pulse later
|
// If we start a new tab with a directory set, we have to set the pseudo class one pulse later
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
@ -66,7 +66,7 @@ public class BrowserOverviewComp extends SimpleComp {
|
||||||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
||||||
|
|
||||||
var recent = BindingsHelper.mappedContentBinding(
|
var recent = ListBindingsHelper.mappedContentBinding(
|
||||||
model.getSavedState().getRecentDirectories(),
|
model.getSavedState().getRecentDirectories(),
|
||||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
|
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
|
||||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||||
|
|
|
@ -52,9 +52,9 @@ public class BrowserSelectionListComp extends SimpleComp {
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var c = new ListBoxViewComp<>(list, list, entry -> {
|
var c = new ListBoxViewComp<>(list, list, entry -> {
|
||||||
return Comp.of(() -> {
|
return Comp.of(() -> {
|
||||||
var wv = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 20)
|
var image = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24)
|
||||||
.createRegion();
|
.createRegion();
|
||||||
var l = new Label(null, wv);
|
var l = new Label(null, image);
|
||||||
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||||
l.textProperty().bind(PlatformThread.sync(nameTransformation.apply(entry)));
|
l.textProperty().bind(PlatformThread.sync(nameTransformation.apply(entry)));
|
||||||
return l;
|
return l;
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> createClipboardStatus() {
|
private Comp<?> createClipboardStatus() {
|
||||||
var cc = BrowserClipboard.currentCopyClipboard;
|
var cc = BrowserClipboard.currentCopyClipboard;
|
||||||
var ccCount = (BindingsHelper.persist(Bindings.createStringBinding(
|
var ccCount = Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (cc.getValue() != null && cc.getValue().getEntries().size() > 0) {
|
if (cc.getValue() != null && cc.getValue().getEntries().size() > 0) {
|
||||||
return cc.getValue().getEntries().size() + " file"
|
return cc.getValue().getEntries().size() + " file"
|
||||||
|
@ -68,7 +68,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cc)));
|
cc);
|
||||||
return new LabelComp(ccCount);
|
return new LabelComp(ccCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
.count();
|
.count();
|
||||||
},
|
},
|
||||||
model.getFileList().getAll());
|
model.getFileList().getAll());
|
||||||
var selectedComp = new LabelComp(BindingsHelper.persist(Bindings.createStringBinding(
|
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (selectedCount.getValue().intValue() == 0) {
|
if (selectedCount.getValue().intValue() == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -95,7 +95,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectedCount,
|
selectedCount,
|
||||||
allCount)));
|
allCount));
|
||||||
return selectedComp;
|
return selectedComp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
import io.xpipe.app.fxcomps.impl.*;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
@ -39,11 +39,11 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
||||||
.visible(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
|
.visible(Bindings.isEmpty(model.getItems()));
|
||||||
var backgroundStack =
|
var backgroundStack =
|
||||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
||||||
|
|
||||||
var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
|
var binding = ListBindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
|
||||||
var list = new BrowserSelectionListComp(
|
var list = new BrowserSelectionListComp(
|
||||||
binding,
|
binding,
|
||||||
entry -> Bindings.createStringBinding(
|
entry -> Bindings.createStringBinding(
|
||||||
|
@ -70,20 +70,20 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
.flatMap(aBoolean ->
|
.flatMap(aBoolean ->
|
||||||
aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
|
aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
|
||||||
.hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
|
.hide(PlatformThread.sync(Bindings.isEmpty(model.getItems())))
|
||||||
.grow(true, false)
|
.grow(true, false)
|
||||||
.apply(struc -> struc.get().setPadding(new Insets(8)));
|
.apply(struc -> struc.get().setPadding(new Insets(8)));
|
||||||
|
|
||||||
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
|
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
|
||||||
model.download();
|
model.download();
|
||||||
})
|
})
|
||||||
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())))
|
.hide(Bindings.isEmpty(model.getItems()))
|
||||||
.disable(PlatformThread.sync(model.getAllDownloaded()))
|
.disable(PlatformThread.sync(model.getAllDownloaded()))
|
||||||
.apply(new FancyTooltipAugment<>("downloadStageDescription"));
|
.apply(new TooltipAugment<>("downloadStageDescription"));
|
||||||
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
||||||
model.clear();
|
model.clear();
|
||||||
})
|
})
|
||||||
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
|
.hide(Bindings.isEmpty(model.getItems()));
|
||||||
var clearPane = Comp.derive(
|
var clearPane = Comp.derive(
|
||||||
new HorizontalComp(List.of(downloadButton, clearButton))
|
new HorizontalComp(List.of(downloadButton, clearButton))
|
||||||
.apply(struc -> struc.get().setSpacing(10)),
|
.apply(struc -> struc.get().setSpacing(10)),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.TileButtonComp;
|
import io.xpipe.app.comp.base.TileButtonComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
|
@ -12,6 +13,7 @@ import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -54,13 +56,14 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
hbox.setSpacing(15);
|
hbox.setSpacing(15);
|
||||||
|
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
var header = new Label("Here you will be able to see where you left off last time.");
|
var header = new Label();
|
||||||
|
header.textProperty().bind(AppI18n.observable("browserWelcomeEmpty"));
|
||||||
vbox.getChildren().add(header);
|
vbox.getChildren().add(header);
|
||||||
hbox.setPadding(new Insets(40, 40, 40, 50));
|
hbox.setPadding(new Insets(40, 40, 40, 50));
|
||||||
return new VBox(hbox);
|
return new VBox(hbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = BindingsHelper.filteredContentBinding(state.getEntries(), e -> {
|
var list = ListBindingsHelper.filteredContentBinding(state.getEntries(), e -> {
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
if (entry.isEmpty()) {
|
if (entry.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -74,13 +77,14 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||||
|
|
||||||
var header = new LabelComp(Bindings.createStringBinding(
|
var headerBinding = BindingsHelper.flatMap(empty,b -> {
|
||||||
() -> {
|
if (b) {
|
||||||
return !empty.get()
|
return AppI18n.observable("browserWelcomeEmpty");
|
||||||
? "You were recently connected to the following systems:"
|
} else {
|
||||||
: "Here you will be able to see where you left off last time.";
|
return AppI18n.observable("browserWelcomeSystems");
|
||||||
},
|
}
|
||||||
empty))
|
});
|
||||||
|
var header = new LabelComp(headerBinding)
|
||||||
.createRegion();
|
.createRegion();
|
||||||
AppFont.setSize(header, 1);
|
AppFont.setSize(header, 1);
|
||||||
vbox.getChildren().add(header);
|
vbox.getChildren().add(header);
|
||||||
|
|
|
@ -9,7 +9,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
@ -97,7 +96,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
||||||
home,
|
home,
|
||||||
model.getCurrentPath().isNull(),
|
model.getCurrentPath().isNull(),
|
||||||
fileList,
|
fileList,
|
||||||
BindingsHelper.persist(model.getCurrentPath().isNull().not())));
|
model.getCurrentPath().isNull().not()));
|
||||||
return stack.createRegion();
|
return stack.createRegion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser.action;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserEntry;
|
import io.xpipe.app.browser.BrowserEntry;
|
||||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
|
@ -39,7 +39,7 @@ public interface LeafAction extends BrowserAction {
|
||||||
if (getShortcut() != null) {
|
if (getShortcut() != null) {
|
||||||
Shortcuts.addShortcut(b, getShortcut());
|
Shortcuts.addShortcut(b, getShortcut());
|
||||||
}
|
}
|
||||||
new FancyTooltipAugment<>(new SimpleStringProperty(getName(model, selected))).augment(b);
|
new TooltipAugment<>(new SimpleStringProperty(getName(model, selected))).augment(b);
|
||||||
var graphic = getIcon(model, selected);
|
var graphic = getIcon(model, selected);
|
||||||
if (graphic != null) {
|
if (graphic != null) {
|
||||||
b.setGraphic(graphic);
|
b.setGraphic(graphic);
|
||||||
|
|
|
@ -15,18 +15,18 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public interface BrowserIconDirectoryType {
|
public abstract class BrowserIconDirectoryType {
|
||||||
|
|
||||||
List<BrowserIconDirectoryType> ALL = new ArrayList<>();
|
private static final List<BrowserIconDirectoryType> ALL = new ArrayList<>();
|
||||||
|
|
||||||
static BrowserIconDirectoryType byId(String id) {
|
public synchronized static BrowserIconDirectoryType byId(String id) {
|
||||||
return ALL.stream()
|
return ALL.stream()
|
||||||
.filter(fileType -> fileType.getId().equals(id))
|
.filter(fileType -> fileType.getId().equals(id))
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadDefinitions() {
|
public synchronized static void loadDefinitions() {
|
||||||
ALL.add(new BrowserIconDirectoryType() {
|
ALL.add(new BrowserIconDirectoryType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,13 +74,17 @@ public interface BrowserIconDirectoryType {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String getId();
|
public static synchronized List<BrowserIconDirectoryType> getAll() {
|
||||||
|
return ALL;
|
||||||
|
}
|
||||||
|
|
||||||
boolean matches(FileSystem.FileEntry entry);
|
public abstract String getId();
|
||||||
|
|
||||||
String getIcon(FileSystem.FileEntry entry, boolean open);
|
public abstract boolean matches(FileSystem.FileEntry entry);
|
||||||
|
|
||||||
class Simple implements BrowserIconDirectoryType {
|
public abstract String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||||
|
|
||||||
|
public static class Simple extends BrowserIconDirectoryType {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
|
@ -12,18 +12,18 @@ import java.nio.file.Files;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public interface BrowserIconFileType {
|
public abstract class BrowserIconFileType {
|
||||||
|
|
||||||
List<BrowserIconFileType> ALL = new ArrayList<>();
|
private static final List<BrowserIconFileType> ALL = new ArrayList<>();
|
||||||
|
|
||||||
static BrowserIconFileType byId(String id) {
|
public synchronized static BrowserIconFileType byId(String id) {
|
||||||
return ALL.stream()
|
return ALL.stream()
|
||||||
.filter(fileType -> fileType.getId().equals(id))
|
.filter(fileType -> fileType.getId().equals(id))
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadDefinitions() {
|
public synchronized static void loadDefinitions() {
|
||||||
AppResources.with(AppResources.XPIPE_MODULE, "file_list.txt", path -> {
|
AppResources.with(AppResources.XPIPE_MODULE, "file_list.txt", path -> {
|
||||||
try (var reader =
|
try (var reader =
|
||||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||||
|
@ -53,14 +53,18 @@ public interface BrowserIconFileType {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String getId();
|
public static synchronized List<BrowserIconFileType> getAll() {
|
||||||
|
return ALL;
|
||||||
|
}
|
||||||
|
|
||||||
boolean matches(FileSystem.FileEntry entry);
|
public abstract String getId();
|
||||||
|
|
||||||
String getIcon();
|
public abstract boolean matches(FileSystem.FileEntry entry);
|
||||||
|
|
||||||
|
public abstract String getIcon();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
class Simple implements BrowserIconFileType {
|
public static class Simple extends BrowserIconFileType {
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final IconVariant icon;
|
private final IconVariant icon;
|
||||||
|
|
|
@ -11,29 +11,27 @@ public class FileIconManager {
|
||||||
|
|
||||||
public static synchronized void loadIfNecessary() {
|
public static synchronized void loadIfNecessary() {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "browser_icons");
|
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "browser_icons", true, false);
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFileIcon(FileSystem.FileEntry entry, boolean open) {
|
public static synchronized String getFileIcon(FileSystem.FileEntry entry, boolean open) {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadIfNecessary();
|
|
||||||
|
|
||||||
var r = entry.resolved();
|
var r = entry.resolved();
|
||||||
if (r.getKind() != FileKind.DIRECTORY) {
|
if (r.getKind() != FileKind.DIRECTORY) {
|
||||||
for (var f : BrowserIconFileType.ALL) {
|
for (var f : BrowserIconFileType.getAll()) {
|
||||||
if (f.matches(r)) {
|
if (f.matches(r)) {
|
||||||
return getIconPath(f.getIcon());
|
return f.getIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (var f : BrowserIconDirectoryType.ALL) {
|
for (var f : BrowserIconDirectoryType.getAll()) {
|
||||||
if (f.matches(r)) {
|
if (f.matches(r)) {
|
||||||
return getIconPath(f.getIcon(r, open));
|
return f.getIcon(r, open);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +40,4 @@ public class FileIconManager {
|
||||||
? (open ? "default_folder_opened.svg" : "default_folder.svg")
|
? (open ? "default_folder_opened.svg" : "default_folder.svg")
|
||||||
: "default_file.svg";
|
: "default_file.svg";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getIconPath(String name) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -25,14 +24,15 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
||||||
var multi = new MultiContentComp(model.getEntries().stream()
|
var multi = new MultiContentComp(model.getEntries().stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
entry -> entry.comp(),
|
entry -> entry.comp(),
|
||||||
entry -> PlatformThread.sync(Bindings.createBooleanBinding(
|
entry -> Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return model.getSelected().getValue().equals(entry);
|
return model.getSelected().getValue().equals(entry);
|
||||||
},
|
},
|
||||||
model.getSelected())))));
|
model.getSelected())))
|
||||||
|
);
|
||||||
|
|
||||||
var pane = new BorderPane();
|
var pane = new BorderPane();
|
||||||
var sidebar = new SideMenuBarComp(model.getSelectedInternal(), model.getEntries());
|
var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries());
|
||||||
pane.setCenter(multi.createRegion());
|
pane.setCenter(multi.createRegion());
|
||||||
pane.setRight(sidebar.createRegion());
|
pane.setRight(sidebar.createRegion());
|
||||||
pane.getStyleClass().add("background");
|
pane.getStyleClass().add("background");
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -50,7 +49,7 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
|
||||||
var graphic = getGraphic();
|
var graphic = getGraphic();
|
||||||
if (graphic instanceof FontIcon f) {
|
if (graphic instanceof FontIcon f) {
|
||||||
// f.iconColorProperty().bind(button.textFillProperty());
|
// f.iconColorProperty().bind(button.textFillProperty());
|
||||||
SimpleChangeListener.apply(button.fontProperty(), c -> {
|
button.fontProperty().subscribe(c -> {
|
||||||
f.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
f.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.css.Size;
|
import javafx.css.Size;
|
||||||
import javafx.css.SizeUnits;
|
import javafx.css.SizeUnits;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -38,12 +37,12 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
button.visibleProperty()
|
button.visibleProperty()
|
||||||
.bind(BindingsHelper.anyMatch(cm.getItems().stream()
|
.bind(ListBindingsHelper.anyMatch(cm.getItems().stream()
|
||||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||||
.toList()));
|
.toList()));
|
||||||
|
|
||||||
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
||||||
SimpleChangeListener.apply(button.fontProperty(), c -> {
|
button.fontProperty().subscribe(c -> {
|
||||||
graphic.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
graphic.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
48
app/src/main/java/io/xpipe/app/comp/base/FontIconComp.java
Normal file
48
app/src/main/java/io/xpipe/app/comp/base/FontIconComp.java
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class FontIconComp extends Comp <FontIconComp.Structure>{
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class Structure implements CompStructure<StackPane> {
|
||||||
|
|
||||||
|
FontIcon icon;
|
||||||
|
StackPane pane;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StackPane get() {
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObservableValue<String> icon;
|
||||||
|
|
||||||
|
public FontIconComp(String icon) {
|
||||||
|
this.icon = new SimpleStringProperty(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FontIconComp.Structure createBase() {
|
||||||
|
var fi = new FontIcon();
|
||||||
|
var obs = PlatformThread.sync(icon);
|
||||||
|
icon.subscribe(val -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
fi.setIconLiteral(val);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var pane = new StackPane(fi);
|
||||||
|
return new FontIconComp.Structure(fi, pane);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
@ -65,7 +64,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
|
||||||
sp.prefHeightProperty().bind(r.prefHeightProperty());
|
sp.prefHeightProperty().bind(r.prefHeightProperty());
|
||||||
r.setDisable(true);
|
r.setDisable(true);
|
||||||
|
|
||||||
SimpleChangeListener.apply(currentValue, n -> {
|
currentValue.subscribe(n -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
// 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())) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.app.comp.base;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
|
@ -88,7 +88,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listView.getChildren().equals(newShown)) {
|
if (!listView.getChildren().equals(newShown)) {
|
||||||
BindingsHelper.setContent(listView.getChildren(), newShown);
|
ListBindingsHelper.setContent(listView.getChildren(), newShown);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
@ -59,7 +58,7 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
||||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
|
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
|
||||||
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
||||||
|
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> {
|
PlatformThread.sync(markdown).subscribe(val -> {
|
||||||
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014
|
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014
|
||||||
try {
|
try {
|
||||||
var file = Files.createTempFile(null, ".html");
|
var file = Files.createTempFile(null, ".html");
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
@ -25,9 +24,11 @@ public class MultiContentComp extends SimpleComp {
|
||||||
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> entry : content.entrySet()) {
|
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> entry : content.entrySet()) {
|
||||||
var region = entry.getKey().createRegion();
|
var region = entry.getKey().createRegion();
|
||||||
stack.getChildren().add(region);
|
stack.getChildren().add(region);
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(entry.getValue()), val -> {
|
entry.getValue().subscribe(val -> {
|
||||||
region.setManaged(val);
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
region.setVisible(val);
|
region.setManaged(val);
|
||||||
|
region.setVisible(val);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return stack;
|
return stack;
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class OsLogoComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var img = BindingsHelper.persist(Bindings.createObjectBinding(
|
var img = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (state.getValue() != SystemStateComp.State.SUCCESS) {
|
if (state.getValue() != SystemStateComp.State.SUCCESS) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -51,10 +51,11 @@ public class OsLogoComp extends SimpleComp {
|
||||||
return getImage(ons.getOsName());
|
return getImage(ons.getOsName());
|
||||||
},
|
},
|
||||||
wrapper.getPersistentState(),
|
wrapper.getPersistentState(),
|
||||||
state));
|
state);
|
||||||
var hide = BindingsHelper.map(img, s -> s != null);
|
var hide = BindingsHelper.map(img, s -> s != null);
|
||||||
return new StackComp(
|
return new StackComp(List.of(
|
||||||
List.of(new SystemStateComp(state).hide(hide), new PrettyImageComp(img, 24, 24).visible(hide)))
|
new SystemStateComp(state).hide(hide),
|
||||||
|
new PrettyImageComp(img, 24, 24).visible(hide)))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.Augment;
|
import io.xpipe.app.fxcomps.augment.Augment;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
@ -73,7 +73,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
var e = entries.get(i);
|
var e = entries.get(i);
|
||||||
var b = new IconButtonComp(e.icon(), () -> value.setValue(e));
|
var b = new IconButtonComp(e.icon(), () -> value.setValue(e));
|
||||||
b.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + i]));
|
b.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + i]));
|
||||||
b.apply(new FancyTooltipAugment<>(e.name()));
|
b.apply(new TooltipAugment<>(e.name()));
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
|
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
|
||||||
|
@ -133,7 +133,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
UserReportComp.show(event.build());
|
UserReportComp.show(event.build());
|
||||||
})
|
})
|
||||||
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size()]))
|
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size()]))
|
||||||
.apply(new FancyTooltipAugment<>("reportIssue"))
|
.apply(new TooltipAugment<>("reportIssue"))
|
||||||
.apply(simpleBorders)
|
.apply(simpleBorders)
|
||||||
.accessibleTextKey("reportIssue");
|
.accessibleTextKey("reportIssue");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
|
@ -145,7 +145,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
|
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
|
||||||
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 1]))
|
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 1]))
|
||||||
.apply(new FancyTooltipAugment<>("visitGithubRepository"))
|
.apply(new TooltipAugment<>("visitGithubRepository"))
|
||||||
.apply(simpleBorders)
|
.apply(simpleBorders)
|
||||||
.accessibleTextKey("visitGithubRepository");
|
.accessibleTextKey("visitGithubRepository");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
|
@ -157,7 +157,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
|
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
|
||||||
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 2]))
|
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 2]))
|
||||||
.apply(new FancyTooltipAugment<>("discord"))
|
.apply(new TooltipAugment<>("discord"))
|
||||||
.apply(simpleBorders)
|
.apply(simpleBorders)
|
||||||
.accessibleTextKey("discord");
|
.accessibleTextKey("discord");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
|
@ -167,9 +167,20 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
var b = new IconButtonComp("mdi2t-translate", () -> Hyperlinks.open(Hyperlinks.TRANSLATE))
|
||||||
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 3]))
|
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 3]))
|
||||||
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"))
|
.apply(new TooltipAugment<>("translate"))
|
||||||
|
.apply(simpleBorders)
|
||||||
|
.accessibleTextKey("translate");
|
||||||
|
b.apply(struc -> {
|
||||||
|
AppFont.setSize(struc.get(), 2);
|
||||||
|
});
|
||||||
|
vbox.getChildren().add(b.createRegion());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
||||||
|
.apply(new TooltipAugment<>("updateAvailableTooltip"))
|
||||||
.accessibleTextKey("updateAvailableTooltip");
|
.accessibleTextKey("updateAvailableTooltip");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
|
||||||
import io.xpipe.app.comp.store.StoreSection;
|
import io.xpipe.app.comp.store.StoreSection;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -32,13 +31,13 @@ public class StoreToggleComp extends SimpleComp {
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var disable = section.getWrapper().getValidity().map(state -> state != DataStoreEntry.Validity.COMPLETE);
|
var disable = section.getWrapper().getValidity().map(state -> state != DataStoreEntry.Validity.COMPLETE);
|
||||||
var visible = BindingsHelper.persist(Bindings.createBooleanBinding(
|
var visible = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.COMPLETE
|
return section.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.COMPLETE
|
||||||
&& section.getShowDetails().get();
|
&& section.getShowDetails().get();
|
||||||
},
|
},
|
||||||
section.getWrapper().getValidity(),
|
section.getWrapper().getValidity(),
|
||||||
section.getShowDetails()));
|
section.getShowDetails());
|
||||||
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
|
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
|
||||||
.visible(visible)
|
.visible(visible)
|
||||||
.disable(disable);
|
.disable(disable);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.core.process.ShellStoreState;
|
import io.xpipe.core.process.ShellStoreState;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -35,7 +34,7 @@ public class SystemStateComp extends SimpleComp {
|
||||||
state));
|
state));
|
||||||
var fi = new FontIcon();
|
var fi = new FontIcon();
|
||||||
fi.getStyleClass().add("inner-icon");
|
fi.getStyleClass().add("inner-icon");
|
||||||
SimpleChangeListener.apply(icon, val -> fi.setIconLiteral(val));
|
icon.subscribe(val -> fi.setIconLiteral(val));
|
||||||
|
|
||||||
var border = new FontIcon("mdi2c-circle-outline");
|
var border = new FontIcon("mdi2c-circle-outline");
|
||||||
border.getStyleClass().add("outer-icon");
|
border.getStyleClass().add("outer-icon");
|
||||||
|
@ -63,7 +62,7 @@ public class SystemStateComp extends SimpleComp {
|
||||||
""";
|
""";
|
||||||
pane.getStylesheets().add(Styles.toDataURI(dataClass1));
|
pane.getStylesheets().add(Styles.toDataURI(dataClass1));
|
||||||
|
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(state), val -> {
|
PlatformThread.sync(state).subscribe(val -> {
|
||||||
pane.getStylesheets().removeAll(success, failure, other);
|
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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -13,7 +12,6 @@ import javafx.event.ActionEvent;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
@ -56,12 +54,8 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
var text = new VBox(header, desc);
|
var text = new VBox(header, desc);
|
||||||
text.setSpacing(2);
|
text.setSpacing(2);
|
||||||
|
|
||||||
var fi = new FontIcon();
|
var fi = new FontIconComp(icon).createStructure();
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(icon), val -> {
|
var pane = fi.getPane();
|
||||||
fi.setIconLiteral(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
var pane = new StackPane(fi);
|
|
||||||
var hbox = new HBox(pane, text);
|
var hbox = new HBox(pane, text);
|
||||||
hbox.setSpacing(8);
|
hbox.setSpacing(8);
|
||||||
pane.prefWidthProperty()
|
pane.prefWidthProperty()
|
||||||
|
@ -76,11 +70,11 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
desc.heightProperty()));
|
desc.heightProperty()));
|
||||||
pane.prefHeightProperty().addListener((c, o, n) -> {
|
pane.prefHeightProperty().addListener((c, o, n) -> {
|
||||||
var size = Math.min(n.intValue(), 100);
|
var size = Math.min(n.intValue(), 100);
|
||||||
fi.setIconSize((int) (size * 0.55));
|
fi.getIcon().setIconSize((int) (size * 0.55));
|
||||||
});
|
});
|
||||||
bt.setGraphic(hbox);
|
bt.setGraphic(hbox);
|
||||||
return Structure.builder()
|
return Structure.builder()
|
||||||
.graphic(fi)
|
.graphic(fi.getIcon())
|
||||||
.button(bt)
|
.button(bt)
|
||||||
.content(hbox)
|
.content(hbox)
|
||||||
.name(header)
|
.name(header)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -80,6 +81,10 @@ public class StoreCategoryWrapper {
|
||||||
|
|
||||||
private void setupListeners() {
|
private void setupListeners() {
|
||||||
name.addListener((c, o, n) -> {
|
name.addListener((c, o, n) -> {
|
||||||
|
if (n.equals(translatedName(category.getName()))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
category.setName(n);
|
category.setName(n);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,6 +96,10 @@ public class StoreCategoryWrapper {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AppPrefs.get().language().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||||
category.setSortMode(newValue);
|
category.setSortMode(newValue);
|
||||||
});
|
});
|
||||||
|
@ -112,8 +121,9 @@ public class StoreCategoryWrapper {
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
// Avoid reupdating name when changed from the name property!
|
// Avoid reupdating name when changed from the name property!
|
||||||
if (!category.getName().equals(name.getValue())) {
|
var catName = translatedName(category.getName());
|
||||||
name.setValue(category.getName());
|
if (!catName.equals(name.getValue())) {
|
||||||
|
name.setValue(catName);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastAccess.setValue(category.getLastAccess().minus(Duration.ofMillis(500)));
|
lastAccess.setValue(category.getLastAccess().minus(Duration.ofMillis(500)));
|
||||||
|
@ -140,18 +150,30 @@ public class StoreCategoryWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
private String translatedName(String original) {
|
||||||
return name.getValue();
|
if (original.equals("All connections")) {
|
||||||
|
return AppI18n.get("allConnections");
|
||||||
|
}
|
||||||
|
if (original.equals("All scripts")) {
|
||||||
|
return AppI18n.get("allScripts");
|
||||||
|
}
|
||||||
|
if (original.equals("Predefined")) {
|
||||||
|
return AppI18n.get("predefined");
|
||||||
|
}
|
||||||
|
if (original.equals("Custom")) {
|
||||||
|
return AppI18n.get("custom");
|
||||||
|
}
|
||||||
|
if (original.equals("Default")) {
|
||||||
|
return AppI18n.get("default");
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Property<String> nameProperty() {
|
public Property<String> nameProperty() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getLastAccess() {
|
|
||||||
return lastAccess.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property<Instant> lastAccessProperty() {
|
public Property<Instant> lastAccessProperty() {
|
||||||
return lastAccess;
|
return lastAccess;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.ExceptionConverter;
|
import io.xpipe.app.issue.ExceptionConverter;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
@ -381,7 +380,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
providerChoice.apply(GrowAugment.create(true, false));
|
providerChoice.apply(GrowAugment.create(true, false));
|
||||||
providerChoice.onSceneAssign(struc -> struc.get().requestFocus());
|
providerChoice.onSceneAssign(struc -> struc.get().requestFocus());
|
||||||
|
|
||||||
SimpleChangeListener.apply(provider, n -> {
|
provider.subscribe(n -> {
|
||||||
if (n != null) {
|
if (n != null) {
|
||||||
var d = n.guiDialog(existingEntry, store);
|
var d = n.guiDialog(existingEntry, store);
|
||||||
var propVal = new SimpleValidator();
|
var propVal = new SimpleValidator();
|
||||||
|
|
|
@ -26,6 +26,9 @@ public class StoreCreationMenu {
|
||||||
|
|
||||||
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("addVisual", "mdi2c-camera-plus", DataStoreProvider.CreationCategory.VISUAL, null));
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
|
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
import io.xpipe.app.fxcomps.impl.*;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreColor;
|
import io.xpipe.app.storage.DataStoreColor;
|
||||||
|
@ -101,8 +99,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
var loading = LoadingOverlayComp.noProgress(
|
var loading = LoadingOverlayComp.noProgress(
|
||||||
Comp.of(() -> button),
|
Comp.of(() -> button),
|
||||||
BindingsHelper.persist(
|
wrapper.getInRefresh().and(wrapper.getObserving().not()));
|
||||||
wrapper.getInRefresh().and(wrapper.getObserving().not())));
|
|
||||||
return loading.createRegion();
|
return loading.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +135,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void applyState(Node node) {
|
protected void applyState(Node node) {
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(wrapper.getValidity()), val -> {
|
PlatformThread.sync(wrapper.getValidity()).subscribe(val -> {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case LOAD_FAILED -> {
|
case LOAD_FAILED -> {
|
||||||
node.pseudoClassStateChanged(FAILED, true);
|
node.pseudoClassStateChanged(FAILED, true);
|
||||||
|
@ -174,7 +171,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
var imageComp = PrettyImageHelper.ofFixedSize(img, w, h);
|
var imageComp = PrettyImageHelper.ofFixedSize(img, w, h);
|
||||||
var storeIcon = imageComp.createRegion();
|
var storeIcon = imageComp.createRegion();
|
||||||
if (wrapper.getValidity().getValue().isUsable()) {
|
if (wrapper.getValidity().getValue().isUsable()) {
|
||||||
new FancyTooltipAugment<>(new SimpleStringProperty(
|
new TooltipAugment<>(new SimpleStringProperty(
|
||||||
wrapper.getEntry().getProvider().getDisplayName()))
|
wrapper.getEntry().getProvider().getDisplayName()))
|
||||||
.augment(storeIcon);
|
.augment(storeIcon);
|
||||||
}
|
}
|
||||||
|
@ -212,7 +209,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
button.accessibleText(
|
button.accessibleText(
|
||||||
actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
||||||
button.apply(new FancyTooltipAugment<>(
|
button.apply(new TooltipAugment<>(
|
||||||
actionProvider.getName(wrapper.getEntry().ref())));
|
actionProvider.getName(wrapper.getEntry().ref())));
|
||||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
||||||
button.hide(Bindings.not(p.getValue()));
|
button.hide(Bindings.not(p.getValue()));
|
||||||
|
@ -248,7 +245,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
settingsButton.accessibleText("More");
|
settingsButton.accessibleText("More");
|
||||||
settingsButton.apply(new ContextMenuAugment<>(
|
settingsButton.apply(new ContextMenuAugment<>(
|
||||||
event -> event.getButton() == MouseButton.PRIMARY, null, () -> StoreEntryComp.this.createContextMenu()));
|
event -> event.getButton() == MouseButton.PRIMARY, null, () -> StoreEntryComp.this.createContextMenu()));
|
||||||
settingsButton.apply(new FancyTooltipAugment<>("more"));
|
settingsButton.apply(new TooltipAugment<>("more"));
|
||||||
return settingsButton;
|
return settingsButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +368,8 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
StoreViewState.get()
|
StoreViewState.get()
|
||||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||||
.forEach(storeCategoryWrapper -> {
|
.forEach(storeCategoryWrapper -> {
|
||||||
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
MenuItem m = new MenuItem();
|
||||||
|
m.textProperty().bind(storeCategoryWrapper.nameProperty());
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||||
event.consume();
|
event.consume();
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -50,16 +49,16 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||||
map.put(
|
map.put(
|
||||||
createList(),
|
createList(),
|
||||||
BindingsHelper.persist(Bindings.not(Bindings.isEmpty(
|
Bindings.not(Bindings.isEmpty(
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
|
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||||
|
|
||||||
map.put(new StoreIntroComp(), showIntro);
|
map.put(new StoreIntroComp(), showIntro);
|
||||||
map.put(
|
map.put(
|
||||||
new StoreNotFoundComp(),
|
new StoreNotFoundComp(),
|
||||||
BindingsHelper.persist(Bindings.and(
|
Bindings.and(
|
||||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
|
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
|
||||||
Bindings.isEmpty(
|
Bindings.isEmpty(
|
||||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
|
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||||
return new MultiContentComp(map).createRegion();
|
return new MultiContentComp(map).createRegion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -36,7 +36,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
|
|
||||||
public StoreEntryListStatusComp() {
|
public StoreEntryListStatusComp() {
|
||||||
this.sortMode = new SimpleObjectProperty<>();
|
this.sortMode = new SimpleObjectProperty<>();
|
||||||
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
|
StoreViewState.get().getActiveCategory().subscribe(val -> {
|
||||||
sortMode.setValue(val.getSortMode().getValue());
|
sortMode.setValue(val.getSortMode().getValue());
|
||||||
});
|
});
|
||||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -51,21 +51,12 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
|
|
||||||
private Region createGroupListHeader() {
|
private Region createGroupListHeader() {
|
||||||
var label = new Label();
|
var label = new Label();
|
||||||
label.textProperty()
|
var name = BindingsHelper.flatMap(StoreViewState.get().getActiveCategory(),
|
||||||
.bind(Bindings.createStringBinding(
|
categoryWrapper -> AppI18n.observable(categoryWrapper.getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "connections" : "scripts"));
|
||||||
() -> {
|
label.textProperty().bind(name);
|
||||||
return StoreViewState.get()
|
|
||||||
.getActiveCategory()
|
|
||||||
.getValue()
|
|
||||||
.getRoot()
|
|
||||||
.equals(StoreViewState.get().getAllConnectionsCategory())
|
|
||||||
? "Connections"
|
|
||||||
: "Scripts";
|
|
||||||
},
|
|
||||||
StoreViewState.get().getActiveCategory()));
|
|
||||||
label.getStyleClass().add("name");
|
label.getStyleClass().add("name");
|
||||||
|
|
||||||
var all = BindingsHelper.filteredContentBinding(
|
var all = ListBindingsHelper.filteredContentBinding(
|
||||||
StoreViewState.get().getAllEntries(),
|
StoreViewState.get().getAllEntries(),
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||||
|
@ -76,7 +67,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
.equals(storeRoot);
|
.equals(storeRoot);
|
||||||
},
|
},
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
var shownList = BindingsHelper.filteredContentBinding(
|
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||||
all,
|
all,
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
return storeEntryWrapper.shouldShow(
|
return storeEntryWrapper.shouldShow(
|
||||||
|
@ -135,7 +126,8 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region createButtons() {
|
private Region createButtons() {
|
||||||
var menu = new MenuButton(AppI18n.get("addConnections"), new FontIcon("mdi2p-plus-thick"));
|
var menu = new MenuButton(null, new FontIcon("mdi2p-plus-thick"));
|
||||||
|
menu.textProperty().bind(AppI18n.observable("addConnections"));
|
||||||
menu.setAlignment(Pos.CENTER);
|
menu.setAlignment(Pos.CENTER);
|
||||||
menu.setTextAlignment(TextAlignment.CENTER);
|
menu.setTextAlignment(TextAlignment.CENTER);
|
||||||
AppFont.medium(menu);
|
AppFont.medium(menu);
|
||||||
|
@ -188,7 +180,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
sortMode));
|
sortMode));
|
||||||
});
|
});
|
||||||
alphabetical.accessibleTextKey("sortAlphabetical");
|
alphabetical.accessibleTextKey("sortAlphabetical");
|
||||||
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
|
alphabetical.apply(new TooltipAugment<>("sortAlphabetical"));
|
||||||
return alphabetical;
|
return alphabetical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +219,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
sortMode));
|
sortMode));
|
||||||
});
|
});
|
||||||
date.accessibleTextKey("sortLastUsed");
|
date.accessibleTextKey("sortLastUsed");
|
||||||
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
|
date.apply(new TooltipAugment<>("sortLastUsed"));
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
@ -64,10 +65,10 @@ public class StoreSection {
|
||||||
|
|
||||||
var c = Comparator.<StoreSection>comparingInt(
|
var c = Comparator.<StoreSection>comparingInt(
|
||||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
||||||
var mappedSortMode = BindingsHelper.mappedBinding(
|
var mappedSortMode = BindingsHelper.flatMap(
|
||||||
category,
|
category,
|
||||||
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||||
return BindingsHelper.orderedContentBinding(
|
return ListBindingsHelper.orderedContentBinding(
|
||||||
list,
|
list,
|
||||||
(o1, o2) -> {
|
(o1, o2) -> {
|
||||||
var current = mappedSortMode.getValue();
|
var current = mappedSortMode.getValue();
|
||||||
|
@ -86,16 +87,16 @@ public class StoreSection {
|
||||||
Predicate<StoreEntryWrapper> entryFilter,
|
Predicate<StoreEntryWrapper> entryFilter,
|
||||||
ObservableStringValue filterString,
|
ObservableStringValue filterString,
|
||||||
ObservableValue<StoreCategoryWrapper> category) {
|
ObservableValue<StoreCategoryWrapper> category) {
|
||||||
var topLevel = BindingsHelper.filteredContentBinding(
|
var topLevel = ListBindingsHelper.filteredContentBinding(
|
||||||
all,
|
all,
|
||||||
section -> {
|
section -> {
|
||||||
return DataStorage.get().isRootEntry(section.getEntry());
|
return DataStorage.get().isRootEntry(section.getEntry());
|
||||||
},
|
},
|
||||||
category);
|
category);
|
||||||
var cached = BindingsHelper.cachedMappedContentBinding(
|
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||||
topLevel, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
topLevel, topLevel, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
||||||
var ordered = sorted(cached, category);
|
var ordered = sorted(cached, category);
|
||||||
var shown = BindingsHelper.filteredContentBinding(
|
var shown = ListBindingsHelper.filteredContentBinding(
|
||||||
ordered,
|
ordered,
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
|
@ -121,7 +122,7 @@ public class StoreSection {
|
||||||
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
|
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> {
|
||||||
// Legacy implementation that does not use children caches. Use for testing
|
// Legacy implementation that does not use children caches. Use for testing
|
||||||
// if (true) return DataStorage.get()
|
// if (true) return DataStorage.get()
|
||||||
// .getDisplayParent(other.getEntry())
|
// .getDisplayParent(other.getEntry())
|
||||||
|
@ -131,10 +132,10 @@ public class StoreSection {
|
||||||
// This check is fast as the children are cached in the storage
|
// This check is fast as the children are cached in the storage
|
||||||
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||||
});
|
});
|
||||||
var cached = BindingsHelper.cachedMappedContentBinding(
|
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||||
allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
allChildren, allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
||||||
var ordered = sorted(cached, category);
|
var ordered = sorted(cached, category);
|
||||||
var filtered = BindingsHelper.filteredContentBinding(
|
var filtered = ListBindingsHelper.filteredContentBinding(
|
||||||
ordered,
|
ordered,
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
|
|
|
@ -7,8 +7,7 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.storage.DataStoreColor;
|
import io.xpipe.app.storage.DataStoreColor;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -40,11 +39,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
||||||
var quickAccessDisabled = BindingsHelper.persist(Bindings.createBooleanBinding(
|
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getShownChildren().isEmpty();
|
return section.getShownChildren().isEmpty();
|
||||||
},
|
},
|
||||||
section.getShownChildren()));
|
section.getShownChildren());
|
||||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
w.executeDefaultAction();
|
w.executeDefaultAction();
|
||||||
|
@ -91,8 +90,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
return "Expand " + section.getWrapper().getName().getValue();
|
return "Expand " + section.getWrapper().getName().getValue();
|
||||||
},
|
},
|
||||||
section.getWrapper().getName()))
|
section.getWrapper().getName()))
|
||||||
.disable(BindingsHelper.persist(
|
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||||
Bindings.size(section.getShownChildren()).isEqualTo(0)))
|
|
||||||
.styleClass("expand-button")
|
.styleClass("expand-button")
|
||||||
.maxHeight(100)
|
.maxHeight(100)
|
||||||
.vgrow();
|
.vgrow();
|
||||||
|
@ -131,7 +129,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||||
// section is actually expanded
|
// section is actually expanded
|
||||||
var listSections = BindingsHelper.filteredContentBinding(
|
var listSections = ListBindingsHelper.filteredContentBinding(
|
||||||
section.getShownChildren(),
|
section.getShownChildren(),
|
||||||
storeSection -> section.getAllChildren().size() <= 20
|
storeSection -> section.getAllChildren().size() <= 20
|
||||||
|| section.getWrapper().getExpanded().get(),
|
|| section.getWrapper().getExpanded().get(),
|
||||||
|
@ -143,26 +141,26 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
.minHeight(0)
|
.minHeight(0)
|
||||||
.hgrow();
|
.hgrow();
|
||||||
|
|
||||||
var expanded = BindingsHelper.persist(Bindings.createBooleanBinding(
|
var expanded = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getWrapper().getExpanded().get()
|
return section.getWrapper().getExpanded().get()
|
||||||
&& section.getShownChildren().size() > 0;
|
&& section.getShownChildren().size() > 0;
|
||||||
},
|
},
|
||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getShownChildren()));
|
section.getShownChildren());
|
||||||
var full = new VerticalComp(List.of(
|
var full = new VerticalComp(List.of(
|
||||||
topEntryList,
|
topEntryList,
|
||||||
Comp.separator().hide(BindingsHelper.persist(expanded.not())),
|
Comp.separator().hide(expanded.not()),
|
||||||
new HorizontalComp(List.of(content))
|
new HorizontalComp(List.of(content))
|
||||||
.styleClass("content")
|
.styleClass("content")
|
||||||
.apply(struc -> struc.get().setFillHeight(true))
|
.apply(struc -> struc.get().setFillHeight(true))
|
||||||
.hide(BindingsHelper.persist(Bindings.or(
|
.hide(Bindings.or(
|
||||||
Bindings.not(section.getWrapper().getExpanded()),
|
Bindings.not(section.getWrapper().getExpanded()),
|
||||||
Bindings.size(section.getShownChildren()).isEqualTo(0))))));
|
Bindings.size(section.getShownChildren()).isEqualTo(0)))));
|
||||||
return full.styleClass("store-entry-section-comp")
|
return full.styleClass("store-entry-section-comp")
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get().setFillWidth(true);
|
struc.get().setFillWidth(true);
|
||||||
SimpleChangeListener.apply(expanded, val -> {
|
expanded.subscribe(val -> {
|
||||||
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
||||||
});
|
});
|
||||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||||
|
@ -170,7 +168,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
struc.get().pseudoClassStateChanged(ROOT, topLevel);
|
struc.get().pseudoClassStateChanged(ROOT, topLevel);
|
||||||
struc.get().pseudoClassStateChanged(SUB, !topLevel);
|
struc.get().pseudoClassStateChanged(SUB, !topLevel);
|
||||||
|
|
||||||
SimpleChangeListener.apply(section.getWrapper().getColor(), val -> {
|
section.getWrapper().getColor().subscribe(val -> {
|
||||||
if (!topLevel) {
|
if (!topLevel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.storage.DataStoreColor;
|
import io.xpipe.app.storage.DataStoreColor;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
@ -101,16 +100,15 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
+ section.getWrapper().getName().getValue();
|
+ section.getWrapper().getName().getValue();
|
||||||
},
|
},
|
||||||
section.getWrapper().getName()))
|
section.getWrapper().getName()))
|
||||||
.disable(BindingsHelper.persist(
|
.disable(Bindings.size(section.getAllChildren()).isEqualTo(0))
|
||||||
Bindings.size(section.getAllChildren()).isEqualTo(0)))
|
|
||||||
.grow(false, true)
|
.grow(false, true)
|
||||||
.styleClass("expand-button");
|
.styleClass("expand-button");
|
||||||
|
|
||||||
var quickAccessDisabled = BindingsHelper.persist(Bindings.createBooleanBinding(
|
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return section.getShownChildren().isEmpty();
|
return section.getShownChildren().isEmpty();
|
||||||
},
|
},
|
||||||
section.getShownChildren()));
|
section.getShownChildren());
|
||||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||||
action.accept(w);
|
action.accept(w);
|
||||||
};
|
};
|
||||||
|
@ -134,7 +132,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||||
// section is actually expanded
|
// section is actually expanded
|
||||||
var listSections = section.getWrapper() != null
|
var listSections = section.getWrapper() != null
|
||||||
? BindingsHelper.filteredContentBinding(
|
? ListBindingsHelper.filteredContentBinding(
|
||||||
section.getShownChildren(),
|
section.getShownChildren(),
|
||||||
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||||
expanded,
|
expanded,
|
||||||
|
@ -149,9 +147,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
list.add(new HorizontalComp(List.of(content))
|
list.add(new HorizontalComp(List.of(content))
|
||||||
.styleClass("content")
|
.styleClass("content")
|
||||||
.apply(struc -> struc.get().setFillHeight(true))
|
.apply(struc -> struc.get().setFillHeight(true))
|
||||||
.hide(BindingsHelper.persist(Bindings.or(
|
.hide(Bindings.or(
|
||||||
Bindings.not(expanded),
|
Bindings.not(expanded),
|
||||||
Bindings.size(section.getAllChildren()).isEqualTo(0)))));
|
Bindings.size(section.getAllChildren()).isEqualTo(0))));
|
||||||
|
|
||||||
var vert = new VerticalComp(list);
|
var vert = new VerticalComp(list);
|
||||||
if (condensedStyle) {
|
if (condensedStyle) {
|
||||||
|
@ -160,7 +158,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
return vert.styleClass("store-section-mini-comp")
|
return vert.styleClass("store-section-mini-comp")
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get().setFillWidth(true);
|
struc.get().setFillWidth(true);
|
||||||
SimpleChangeListener.apply(expanded, val -> {
|
expanded.subscribe(val -> {
|
||||||
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
||||||
});
|
});
|
||||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||||
|
@ -171,7 +169,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
})
|
})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
if (section.getWrapper() != null) {
|
if (section.getWrapper() != null) {
|
||||||
SimpleChangeListener.apply(section.getWrapper().getColor(), val -> {
|
section.getWrapper().getColor().subscribe(val -> {
|
||||||
if (section.getDepth() != 1) {
|
if (section.getDepth() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -270,10 +270,10 @@ public class StoreViewState {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return o1.getName().compareToIgnoreCase(o2.getName());
|
return o1.nameProperty().getValue().compareToIgnoreCase(o2.nameProperty().getValue());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return BindingsHelper.filteredContentBinding(
|
return ListBindingsHelper.filteredContentBinding(
|
||||||
categories, cat -> root == null || cat.getRoot().equals(root))
|
categories, cat -> root == null || cat.getRoot().equals(root))
|
||||||
.sorted(comparator);
|
.sorted(comparator);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.core;
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
@ -98,7 +97,7 @@ public class AppGreetings {
|
||||||
alert.getButtonTypes().add(buttonType);
|
alert.getButtonTypes().add(buttonType);
|
||||||
|
|
||||||
Button button = (Button) alert.getDialogPane().lookupButton(buttonType);
|
Button button = (Button) alert.getDialogPane().lookupButton(buttonType);
|
||||||
button.disableProperty().bind(BindingsHelper.persist(accepted.not()));
|
button.disableProperty().bind(accepted.not());
|
||||||
}
|
}
|
||||||
|
|
||||||
alert.getButtonTypes().add(ButtonType.CANCEL);
|
alert.getButtonTypes().add(ButtonType.CANCEL);
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
@ -10,101 +10,70 @@ import io.xpipe.app.prefs.SupportedLocale;
|
||||||
import io.xpipe.app.util.OptionsBuilder;
|
import io.xpipe.app.util.OptionsBuilder;
|
||||||
import io.xpipe.app.util.Translatable;
|
import io.xpipe.app.util.Translatable;
|
||||||
import io.xpipe.core.util.ModuleHelper;
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.Value;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.ocpsoft.prettytime.PrettyTime;
|
import org.ocpsoft.prettytime.PrettyTime;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.time.Duration;
|
import java.util.*;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class AppI18n {
|
public class AppI18n {
|
||||||
|
|
||||||
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
@Value
|
||||||
private static final AppI18n INSTANCE = new AppI18n();
|
static class LoadedTranslations {
|
||||||
private Map<String, String> translations;
|
|
||||||
private Map<String, String> markdownDocumentations;
|
|
||||||
private PrettyTime prettyTime;
|
|
||||||
|
|
||||||
public static void init() {
|
Map<String, String> translations;
|
||||||
var i = INSTANCE;
|
Map<String, String> markdownDocumentations;
|
||||||
if (i.translations != null) {
|
PrettyTime prettyTime;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
||||||
|
private static AppI18n INSTANCE;
|
||||||
|
private LoadedTranslations english;
|
||||||
|
private final Property<LoadedTranslations> currentLanguage = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
public static void init() throws Exception {
|
||||||
|
INSTANCE = new AppI18n();
|
||||||
|
INSTANCE.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() throws Exception {
|
||||||
|
if (english == null) {
|
||||||
|
english = load(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
i.load();
|
|
||||||
|
|
||||||
if (AppPrefs.get() != null) {
|
if (AppPrefs.get() != null) {
|
||||||
AppPrefs.get().language().addListener((c, o, n) -> {
|
AppPrefs.get().language().subscribe(n -> {
|
||||||
i.clear();
|
try {
|
||||||
i.load();
|
currentLanguage.setValue(n != null ? load(n.getLocale()) : null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppI18n getInstance() {
|
public static AppI18n get() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StringBinding readableInstant(String s, ObservableValue<Instant> instant) {
|
private LoadedTranslations getLoaded() {
|
||||||
return readableInstant(instant, rs -> getValue(getInstance().getLocalised(s), rs));
|
return currentLanguage.getValue() != null ? currentLanguage.getValue() : english;
|
||||||
}
|
|
||||||
|
|
||||||
public static StringBinding readableInstant(ObservableValue<Instant> instant, UnaryOperator<String> op) {
|
|
||||||
return Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
if (instant.getValue() == null) {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
|
|
||||||
return op.apply(
|
|
||||||
getInstance().prettyTime.format(instant.getValue().minus(Duration.ofSeconds(1))));
|
|
||||||
},
|
|
||||||
instant);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StringBinding readableInstant(ObservableValue<Instant> instant) {
|
|
||||||
return Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
if (instant.getValue() == null) {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
|
|
||||||
return getInstance().prettyTime.format(instant.getValue().minus(Duration.ofSeconds(1)));
|
|
||||||
},
|
|
||||||
instant);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StringBinding readableDuration(ObservableValue<Duration> duration) {
|
|
||||||
return Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
if (duration.getValue() == null) {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
|
|
||||||
return getInstance()
|
|
||||||
.prettyTime
|
|
||||||
.formatDuration(getInstance()
|
|
||||||
.prettyTime
|
|
||||||
.approximateDuration(Instant.now().plus(duration.getValue())));
|
|
||||||
},
|
|
||||||
duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ObservableValue<String> observable(String s, Object... vars) {
|
public static ObservableValue<String> observable(String s, Object... vars) {
|
||||||
|
@ -115,7 +84,7 @@ public class AppI18n {
|
||||||
var key = INSTANCE.getKey(s);
|
var key = INSTANCE.getKey(s);
|
||||||
return Bindings.createStringBinding(() -> {
|
return Bindings.createStringBinding(() -> {
|
||||||
return get(key, vars);
|
return get(key, vars);
|
||||||
});
|
}, INSTANCE.currentLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String get(String s, Object... vars) {
|
public static String get(String s, Object... vars) {
|
||||||
|
@ -147,7 +116,7 @@ public class AppI18n {
|
||||||
|| caller.equals(ModuleHelper.class)
|
|| caller.equals(ModuleHelper.class)
|
||||||
|| caller.equals(ModalOverlayComp.class)
|
|| caller.equals(ModalOverlayComp.class)
|
||||||
|| caller.equals(AppI18n.class)
|
|| caller.equals(AppI18n.class)
|
||||||
|| caller.equals(FancyTooltipAugment.class)
|
|| caller.equals(TooltipAugment.class)
|
||||||
|| caller.equals(PrefsChoiceValue.class)
|
|| caller.equals(PrefsChoiceValue.class)
|
||||||
|| caller.equals(Translatable.class)
|
|| caller.equals(Translatable.class)
|
||||||
|| caller.equals(AppWindowHelper.class)
|
|| caller.equals(AppWindowHelper.class)
|
||||||
|
@ -160,11 +129,6 @@ public class AppI18n {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear() {
|
|
||||||
translations.clear();
|
|
||||||
prettyTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKey(String s) {
|
public String getKey(String s) {
|
||||||
var key = s;
|
var key = s;
|
||||||
if (!s.contains(".")) {
|
if (!s.contains(".")) {
|
||||||
|
@ -173,62 +137,62 @@ public class AppI18n {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsKey(String s) {
|
|
||||||
var key = getKey(s);
|
|
||||||
if (translations == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return translations.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLocalised(String s, Object... vars) {
|
public String getLocalised(String s, Object... vars) {
|
||||||
var key = getKey(s);
|
var key = getKey(s);
|
||||||
|
|
||||||
if (translations == null) {
|
if (english == null) {
|
||||||
TrackEvent.warn("Translations not initialized for " + key);
|
TrackEvent.warn("Translations not initialized for " + key);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!translations.containsKey(key)) {
|
if (currentLanguage.getValue() != null && currentLanguage.getValue().getTranslations().containsKey(key)) {
|
||||||
TrackEvent.warn("Translation key not found for " + key);
|
var localisedString = currentLanguage.getValue().getTranslations().get(key);
|
||||||
return key;
|
return getValue(localisedString, vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
var localisedString = translations.get(key);
|
if (english.getTranslations().containsKey(key)) {
|
||||||
return getValue(localisedString, vars);
|
var localisedString = english.getTranslations().get(key);
|
||||||
|
return getValue(localisedString, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackEvent.warn("Translation key not found for " + key);
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoaded() {
|
private boolean matchesLocale(Path f, Locale l) {
|
||||||
return translations != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchesLocale(Path f) {
|
|
||||||
var l = AppPrefs.get() != null
|
|
||||||
? AppPrefs.get().language().getValue().getLocale()
|
|
||||||
: SupportedLocale.ENGLISH.getLocale();
|
|
||||||
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
||||||
var ending = "_" + l.toLanguageTag();
|
var ending = "_" + l.toLanguageTag();
|
||||||
return name.endsWith(ending);
|
return name.endsWith(ending);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMarkdownDocumentation(String name) {
|
public String getMarkdownDocumentation(String name) {
|
||||||
if (!markdownDocumentations.containsKey(name)) {
|
if (currentLanguage.getValue() != null && currentLanguage.getValue().getMarkdownDocumentations().containsKey(name)) {
|
||||||
TrackEvent.withWarn("Markdown documentation for key " + name + " not found")
|
var localisedString = currentLanguage.getValue().getMarkdownDocumentations().get(name);
|
||||||
.handle();
|
return localisedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return markdownDocumentations.getOrDefault(name, "");
|
if (english.getMarkdownDocumentations().containsKey(name)) {
|
||||||
|
var localisedString = english.getMarkdownDocumentations().get(name);
|
||||||
|
return localisedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackEvent.withWarn("Markdown documentation for key " + name + " not found")
|
||||||
|
.handle();
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private Path getModuleLangPath(String module) {
|
||||||
|
return XPipeInstallation.getLangPath().resolve(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoadedTranslations load(Locale l) throws Exception {
|
||||||
TrackEvent.info("Loading translations ...");
|
TrackEvent.info("Loading translations ...");
|
||||||
|
|
||||||
translations = new HashMap<>();
|
var translations = new HashMap<String, String>();
|
||||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||||
AppResources.with(module.getName(), "lang", basePath -> {
|
var basePath = getModuleLangPath(FilenameUtils.getExtension(module.getName())).resolve("strings");
|
||||||
if (!Files.exists(basePath)) {
|
if (!Files.exists(basePath)) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AtomicInteger fileCounter = new AtomicInteger();
|
AtomicInteger fileCounter = new AtomicInteger();
|
||||||
|
@ -238,7 +202,7 @@ public class AppI18n {
|
||||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||||
if (!matchesLocale(file)) {
|
if (!matchesLocale(file, l)) {
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +213,7 @@ public class AppI18n {
|
||||||
fileCounter.incrementAndGet();
|
fileCounter.incrementAndGet();
|
||||||
try (var in = Files.newInputStream(file)) {
|
try (var in = Files.newInputStream(file)) {
|
||||||
var props = new Properties();
|
var props = new Properties();
|
||||||
props.load(in);
|
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||||
props.forEach((key, value) -> {
|
props.forEach((key, value) -> {
|
||||||
var hasPrefix = key.toString().contains(".");
|
var hasPrefix = key.toString().contains(".");
|
||||||
var usedPrefix = hasPrefix ? "" : defaultPrefix;
|
var usedPrefix = hasPrefix ? "" : defaultPrefix;
|
||||||
|
@ -267,21 +231,20 @@ public class AppI18n {
|
||||||
.tag("fileCount", fileCounter.get())
|
.tag("fileCount", fileCounter.get())
|
||||||
.tag("lineCount", lineCounter.get())
|
.tag("lineCount", lineCounter.get())
|
||||||
.handle();
|
.handle();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
markdownDocumentations = new HashMap<>();
|
var markdownDocumentations = new HashMap<String, String>();
|
||||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||||
AppResources.with(module.getName(), "lang", basePath -> {
|
var basePath = getModuleLangPath(FilenameUtils.getExtension(module.getName())).resolve("texts");
|
||||||
if (!Files.exists(basePath)) {
|
if (!Files.exists(basePath)) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var moduleName = FilenameUtils.getExtension(module.getName());
|
var moduleName = FilenameUtils.getExtension(module.getName());
|
||||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||||
if (!matchesLocale(file)) {
|
if (!matchesLocale(file, l)) {
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,13 +265,14 @@ public class AppI18n {
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prettyTime = new PrettyTime(
|
var prettyTime = new PrettyTime(
|
||||||
AppPrefs.get() != null
|
AppPrefs.get() != null
|
||||||
? AppPrefs.get().language().getValue().getLocale()
|
? AppPrefs.get().language().getValue().getLocale()
|
||||||
: SupportedLocale.ENGLISH.getLocale());
|
: SupportedLocale.ENGLISH.getLocale());
|
||||||
|
|
||||||
|
return new LoadedTranslations(translations,markdownDocumentations, prettyTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
|
|
|
@ -30,11 +30,11 @@ public class AppImages {
|
||||||
|
|
||||||
TrackEvent.info("Loading images ...");
|
TrackEvent.info("Loading images ...");
|
||||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||||
loadDirectory(module.getName(), "img");
|
loadDirectory(module.getName(), "img", true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadDirectory(String module, String dir) {
|
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
|
||||||
AppResources.with(module, dir, basePath -> {
|
AppResources.with(module, dir, basePath -> {
|
||||||
if (!Files.exists(basePath)) {
|
if (!Files.exists(basePath)) {
|
||||||
return;
|
return;
|
||||||
|
@ -48,10 +48,10 @@ public class AppImages {
|
||||||
var relativeFileName = FilenameUtils.separatorsToUnix(
|
var relativeFileName = FilenameUtils.separatorsToUnix(
|
||||||
basePath.relativize(file).toString());
|
basePath.relativize(file).toString());
|
||||||
try {
|
try {
|
||||||
if (FilenameUtils.getExtension(file.toString()).equals("svg")) {
|
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
|
||||||
var s = Files.readString(file);
|
var s = Files.readString(file);
|
||||||
svgImages.put(defaultPrefix + relativeFileName, s);
|
svgImages.put(defaultPrefix + relativeFileName, s);
|
||||||
} else {
|
} else if (loadImages) {
|
||||||
images.put(defaultPrefix + relativeFileName, loadImage(file));
|
images.put(defaultPrefix + relativeFileName, loadImage(file));
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
|
|
@ -2,10 +2,8 @@ package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserComp;
|
import io.xpipe.app.browser.BrowserComp;
|
||||||
import io.xpipe.app.browser.BrowserModel;
|
import io.xpipe.app.browser.BrowserModel;
|
||||||
import io.xpipe.app.comp.DeveloperTabComp;
|
|
||||||
import io.xpipe.app.comp.store.StoreLayoutComp;
|
import io.xpipe.app.comp.store.StoreLayoutComp;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
|
||||||
import io.xpipe.app.prefs.AppPrefsComp;
|
import io.xpipe.app.prefs.AppPrefsComp;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
@ -30,13 +28,11 @@ public class AppLayoutModel {
|
||||||
private final List<Entry> entries;
|
private final List<Entry> entries;
|
||||||
|
|
||||||
private final Property<Entry> selected;
|
private final Property<Entry> selected;
|
||||||
private final ObservableValue<Entry> selectedWrapper;
|
|
||||||
|
|
||||||
public AppLayoutModel(SavedState savedState) {
|
public AppLayoutModel(SavedState savedState) {
|
||||||
this.savedState = savedState;
|
this.savedState = savedState;
|
||||||
this.entries = createEntryList();
|
this.entries = createEntryList();
|
||||||
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
||||||
this.selectedWrapper = PlatformThread.sync(selected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppLayoutModel get() {
|
public static AppLayoutModel get() {
|
||||||
|
@ -53,14 +49,10 @@ public class AppLayoutModel {
|
||||||
INSTANCE = null;
|
INSTANCE = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Property<Entry> getSelectedInternal() {
|
public Property<Entry> getSelected() {
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableValue<Entry> getSelected() {
|
|
||||||
return selectedWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectBrowser() {
|
public void selectBrowser() {
|
||||||
selected.setValue(entries.getFirst());
|
selected.setValue(entries.getFirst());
|
||||||
}
|
}
|
||||||
|
@ -81,19 +73,11 @@ public class AppLayoutModel {
|
||||||
var l = new ArrayList<>(List.of(
|
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("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
|
new Entry(
|
||||||
// StorageLayoutComp()),
|
AppI18n.observable("explorePlans"),
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
|
"mdi2p-professional-hexagon",
|
||||||
if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) {
|
LicenseProvider.get().overviewPage())));
|
||||||
l.add(new Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
|
||||||
}
|
|
||||||
|
|
||||||
l.add(new Entry(
|
|
||||||
AppI18n.observable("explorePlans"),
|
|
||||||
"mdi2p-professional-hexagon",
|
|
||||||
LicenseProvider.get().overviewPage()));
|
|
||||||
|
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.core;
|
||||||
import atlantafx.base.theme.*;
|
import atlantafx.base.theme.*;
|
||||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
@ -44,7 +43,7 @@ public class AppTheme {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleChangeListener.apply(AppPrefs.get().theme, t -> {
|
AppPrefs.get().theme.subscribe(t -> {
|
||||||
Theme.ALL.forEach(
|
Theme.ALL.forEach(
|
||||||
theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));
|
theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
|
@ -56,7 +55,7 @@ public class AppTheme {
|
||||||
stage.getScene().getRoot().pseudoClassStateChanged(DARK, t.isDark());
|
stage.getScene().getRoot().pseudoClassStateChanged(DARK, t.isDark());
|
||||||
});
|
});
|
||||||
|
|
||||||
SimpleChangeListener.apply(AppPrefs.get().performanceMode(), val -> {
|
AppPrefs.get().performanceMode().subscribe(val -> {
|
||||||
stage.getScene().getRoot().pseudoClassStateChanged(PRETTY, !val);
|
stage.getScene().getRoot().pseudoClassStateChanged(PRETTY, !val);
|
||||||
stage.getScene().getRoot().pseudoClassStateChanged(PERFORMANCE, val);
|
stage.getScene().getRoot().pseudoClassStateChanged(PERFORMANCE, val);
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,6 +47,7 @@ public class BaseMode extends OperationMode {
|
||||||
AppI18n.init();
|
AppI18n.init();
|
||||||
LicenseProvider.get().init();
|
LicenseProvider.get().init();
|
||||||
AppPrefs.initLocal();
|
AppPrefs.initLocal();
|
||||||
|
AppI18n.init();
|
||||||
AppCertutilCheck.check();
|
AppCertutilCheck.check();
|
||||||
AppAvCheck.check();
|
AppAvCheck.check();
|
||||||
AppSid.init();
|
AppSid.init();
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.exchange;
|
||||||
|
|
||||||
import io.xpipe.beacon.BeaconHandler;
|
import io.xpipe.beacon.BeaconHandler;
|
||||||
import io.xpipe.beacon.exchange.LaunchExchange;
|
import io.xpipe.beacon.exchange.LaunchExchange;
|
||||||
import io.xpipe.core.process.TerminalInitScriptConfig;
|
|
||||||
import io.xpipe.core.store.LaunchableStore;
|
import io.xpipe.core.store.LaunchableStore;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -16,9 +15,9 @@ public class LaunchExchangeImpl extends LaunchExchange
|
||||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||||
var store = getStoreEntryById(msg.getId(), false);
|
var store = getStoreEntryById(msg.getId(), false);
|
||||||
if (store.getStore() instanceof LaunchableStore s) {
|
if (store.getStore() instanceof LaunchableStore s) {
|
||||||
var command = s.prepareLaunchCommand()
|
// var command = s.prepareLaunchCommand()
|
||||||
.prepareTerminalOpen(TerminalInitScriptConfig.ofName(store.getName()), sc -> null);
|
// .prepareTerminalOpen(TerminalInitScriptConfig.ofName(store.getName()), sc -> null);
|
||||||
return Response.builder().command(split(command)).build();
|
// return Response.builder().command(split(command)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException(store.getName() + " is not launchable");
|
throw new IllegalArgumentException(store.getName() + " is not launchable");
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package io.xpipe.app.exchange.cli;
|
|
||||||
|
|
||||||
import io.xpipe.app.exchange.MessageExchangeImpl;
|
|
||||||
import io.xpipe.app.update.XPipeInstanceHelper;
|
|
||||||
import io.xpipe.beacon.BeaconHandler;
|
|
||||||
import io.xpipe.beacon.exchange.cli.InstanceExchange;
|
|
||||||
import io.xpipe.core.store.LocalStore;
|
|
||||||
|
|
||||||
public class InstanceExchangeImpl extends InstanceExchange
|
|
||||||
implements MessageExchangeImpl<InstanceExchange.Request, InstanceExchange.Response> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
|
||||||
return Response.builder()
|
|
||||||
.instance(XPipeInstanceHelper.getInstance(new LocalStore()).orElseThrow())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -201,6 +201,7 @@ public interface DataStoreProvider {
|
||||||
COMMAND,
|
COMMAND,
|
||||||
TUNNEL,
|
TUNNEL,
|
||||||
SCRIPT,
|
SCRIPT,
|
||||||
CLUSTER
|
CLUSTER,
|
||||||
|
VISUAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import atlantafx.base.controls.Spacer;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.augment.Augment;
|
import io.xpipe.app.fxcomps.augment.Augment;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -144,7 +144,8 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||||
public Comp<S> hide(ObservableValue<Boolean> o) {
|
public Comp<S> hide(ObservableValue<Boolean> o) {
|
||||||
return apply(struc -> {
|
return apply(struc -> {
|
||||||
var region = struc.get();
|
var region = struc.get();
|
||||||
SimpleChangeListener.apply(o, n -> {
|
BindingsHelper.preserve(region, o);
|
||||||
|
o.subscribe(n -> {
|
||||||
if (!n) {
|
if (!n) {
|
||||||
region.setVisible(true);
|
region.setVisible(true);
|
||||||
region.setManaged(true);
|
region.setManaged(true);
|
||||||
|
@ -189,11 +190,11 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<S> tooltip(ObservableValue<String> text) {
|
public Comp<S> tooltip(ObservableValue<String> text) {
|
||||||
return apply(new FancyTooltipAugment<>(text));
|
return apply(new TooltipAugment<>(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<S> tooltipKey(String key) {
|
public Comp<S> tooltipKey(String key) {
|
||||||
return apply(new FancyTooltipAugment<>(key));
|
return apply(new TooltipAugment<>(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Region createRegion() {
|
public Region createRegion() {
|
||||||
|
@ -202,6 +203,8 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||||
|
|
||||||
public S createStructure() {
|
public S createStructure() {
|
||||||
S struc = createBase();
|
S struc = createBase();
|
||||||
|
// Make comp last at least as long as region
|
||||||
|
BindingsHelper.preserve(struc.get(), this);
|
||||||
if (augments != null) {
|
if (augments != null) {
|
||||||
for (var a : augments) {
|
for (var a : augments) {
|
||||||
a.augment(struc);
|
a.augment(struc);
|
||||||
|
|
|
@ -4,9 +4,8 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.util.Translatable;
|
import io.xpipe.app.util.Translatable;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
@ -72,19 +71,19 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SimpleChangeListener.apply(range, c -> {
|
range.subscribe(c -> {
|
||||||
var list = FXCollections.observableArrayList(c.keySet());
|
var list = FXCollections.observableArrayList(c.keySet());
|
||||||
if (!list.contains(null) && includeNone) {
|
if (!list.contains(null) && includeNone) {
|
||||||
list.add(null);
|
list.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
BindingsHelper.setContent(cb.getItems(), list);
|
ListBindingsHelper.setContent(cb.getItems(), list);
|
||||||
});
|
});
|
||||||
|
|
||||||
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
value.setValue(newValue);
|
value.setValue(newValue);
|
||||||
});
|
});
|
||||||
SimpleChangeListener.apply(value, val -> {
|
value.subscribe(val -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> cb.valueProperty().set(val));
|
PlatformThread.runLaterIfNeeded(() -> cb.valueProperty().set(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
@ -58,7 +57,7 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
||||||
var vbox = new VBox(transformer.apply(cb));
|
var vbox = new VBox(transformer.apply(cb));
|
||||||
vbox.setFillWidth(true);
|
vbox.setFillWidth(true);
|
||||||
cb.prefWidthProperty().bind(vbox.widthProperty());
|
cb.prefWidthProperty().bind(vbox.widthProperty());
|
||||||
SimpleChangeListener.apply(cb.valueProperty(), n -> {
|
cb.valueProperty().subscribe(n -> {
|
||||||
if (n == null) {
|
if (n == null) {
|
||||||
if (vbox.getChildren().size() > 1) {
|
if (vbox.getChildren().size() > 1) {
|
||||||
vbox.getChildren().remove(1);
|
vbox.getChildren().remove(1);
|
||||||
|
@ -82,7 +81,7 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
||||||
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
selected.setValue(newValue);
|
selected.setValue(newValue);
|
||||||
});
|
});
|
||||||
SimpleChangeListener.apply(selected, val -> {
|
selected.subscribe(val -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> cb.valueProperty().set(val));
|
PlatformThread.runLaterIfNeeded(() -> cb.valueProperty().set(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@ import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.browser.StandaloneFileBrowser;
|
import io.xpipe.app.browser.StandaloneFileBrowser;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.core.AppWindowHelper;
|
import io.xpipe.app.core.AppWindowHelper;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.ContextualFileReference;
|
import io.xpipe.app.storage.ContextualFileReference;
|
||||||
|
@ -39,7 +38,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
|
||||||
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
|
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
|
||||||
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
|
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
|
||||||
this.fileSystem = new SimpleObjectProperty<>();
|
this.fileSystem = new SimpleObjectProperty<>();
|
||||||
SimpleChangeListener.apply(fileSystem, val -> {
|
fileSystem.subscribe(val -> {
|
||||||
this.fileSystem.setValue(val);
|
this.fileSystem.setValue(val);
|
||||||
});
|
});
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
|
@ -66,7 +65,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
|
||||||
.styleClass(Styles.CENTER_PILL)
|
.styleClass(Styles.CENTER_PILL)
|
||||||
.grow(false, true);
|
.grow(false, true);
|
||||||
|
|
||||||
var canGitShare = BindingsHelper.persist(Bindings.createBooleanBinding(
|
var canGitShare = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (!AppPrefs.get().enableGitStorage().get()
|
if (!AppPrefs.get().enableGitStorage().get()
|
||||||
|| filePath.getValue() == null
|
|| filePath.getValue() == null
|
||||||
|
@ -77,8 +76,18 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
filePath,
|
filePath,
|
||||||
AppPrefs.get().enableGitStorage()));
|
AppPrefs.get().enableGitStorage());
|
||||||
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
|
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
|
||||||
|
if (!AppPrefs.get().enableGitStorage().get()) {
|
||||||
|
AppLayoutModel.get().selectSettings();
|
||||||
|
AppPrefs.get().selectCategory(3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.getValue() == null || ContextualFileReference.of(filePath.getValue()).isInDataDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (filePath.getValue() == null || filePath.getValue().isBlank() || !canGitShare.get()) {
|
if (filePath.getValue() == null || filePath.getValue().isBlank() || !canGitShare.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -108,7 +117,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
gitShareButton.apply(new FancyTooltipAugment<>("gitShareFileTooltip"));
|
gitShareButton.apply(new TooltipAugment<>("gitShareFileTooltip"));
|
||||||
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
|
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
|
||||||
|
|
||||||
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton))
|
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton))
|
||||||
|
|
|
@ -27,11 +27,11 @@ public class DataStoreFlowChoiceComp extends SimpleComp {
|
||||||
map.put(DataFlow.INPUT_OUTPUT, AppI18n.observable("app.inout"));
|
map.put(DataFlow.INPUT_OUTPUT, AppI18n.observable("app.inout"));
|
||||||
return new ToggleGroupComp<>(selected, new SimpleObjectProperty<>(map))
|
return new ToggleGroupComp<>(selected, new SimpleObjectProperty<>(map))
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
new FancyTooltipAugment<>("app.inputDescription")
|
new TooltipAugment<>("app.inputDescription")
|
||||||
.augment(struc.get().getChildren().get(0));
|
.augment(struc.get().getChildren().get(0));
|
||||||
new FancyTooltipAugment<>("app.outputDescription")
|
new TooltipAugment<>("app.outputDescription")
|
||||||
.augment(struc.get().getChildren().get(1));
|
.augment(struc.get().getChildren().get(1));
|
||||||
new FancyTooltipAugment<>("app.inoutDescription")
|
new TooltipAugment<>("app.inoutDescription")
|
||||||
.augment(struc.get().getChildren().get(2));
|
.augment(struc.get().getChildren().get(2));
|
||||||
})
|
})
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.app.fxcomps.impl;
|
package io.xpipe.app.fxcomps.impl;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppActionLinkDetector;
|
import io.xpipe.app.core.AppActionLinkDetector;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
@ -28,12 +28,13 @@ public class FilterComp extends Comp<FilterComp.Structure> {
|
||||||
@Override
|
@Override
|
||||||
public Structure createBase() {
|
public Structure createBase() {
|
||||||
var fi = new FontIcon("mdi2m-magnify");
|
var fi = new FontIcon("mdi2m-magnify");
|
||||||
var bgLabel = new Label("Search", fi);
|
var bgLabel = new Label(null, fi);
|
||||||
|
bgLabel.textProperty().bind(AppI18n.observable("searchFilter"));
|
||||||
bgLabel.getStyleClass().add("filter-background");
|
bgLabel.getStyleClass().add("filter-background");
|
||||||
var filter = new TextField();
|
var filter = new TextField();
|
||||||
filter.setAccessibleText("Filter");
|
filter.setAccessibleText("Filter");
|
||||||
|
|
||||||
SimpleChangeListener.apply(filterText, val -> {
|
filterText.subscribe(val -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
if (!Objects.equals(filter.getText(), val)) {
|
if (!Objects.equals(filter.getText(), val)) {
|
||||||
filter.setText(val);
|
filter.setText(val);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.core.AppImages;
|
import io.xpipe.app.core.AppImages;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
@ -106,7 +105,7 @@ public class PrettyImageComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(value), val -> update.accept(val));
|
PlatformThread.sync(value).subscribe(val -> update.accept(val));
|
||||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||||
update.accept(value.getValue());
|
update.accept(value.getValue());
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.core.AppImages;
|
import io.xpipe.app.core.AppImages;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -92,7 +91,7 @@ public class PrettySvgComp extends SimpleComp {
|
||||||
image.set(fixed);
|
image.set(fixed);
|
||||||
};
|
};
|
||||||
|
|
||||||
SimpleChangeListener.apply(syncValue, val -> update.accept(val));
|
syncValue.subscribe(val -> update.accept(val));
|
||||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||||
update.accept(syncValue.getValue());
|
update.accept(syncValue.getValue());
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,8 +11,7 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
|
@ -77,7 +76,7 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
showing.bind(cm.showingProperty());
|
showing.bind(cm.showingProperty());
|
||||||
return cm;
|
return cm;
|
||||||
}));
|
}));
|
||||||
var shownList = BindingsHelper.filteredContentBinding(
|
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||||
category.getContainedEntries(),
|
category.getContainedEntries(),
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
return storeEntryWrapper.shouldShow(
|
return storeEntryWrapper.shouldShow(
|
||||||
|
@ -92,9 +91,8 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
Comp.hspacer(4),
|
Comp.hspacer(4),
|
||||||
Comp.of(() -> name),
|
Comp.of(() -> name),
|
||||||
Comp.hspacer(),
|
Comp.hspacer(),
|
||||||
count.hide(BindingsHelper.persist(hover.or(showing).or(focus))),
|
count.hide(hover.or(showing).or(focus)),
|
||||||
settings.hide(
|
settings.hide(hover.not().and(showing.not()).and(focus.not()))));
|
||||||
BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not())))));
|
|
||||||
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
|
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
|
||||||
|
|
||||||
var categoryButton = new ButtonComp(null, h.createRegion(), category::select)
|
var categoryButton = new ButtonComp(null, h.createRegion(), category::select)
|
||||||
|
@ -108,14 +106,14 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
|
|
||||||
var l = category.getChildren()
|
var l = category.getChildren()
|
||||||
.sorted(Comparator.comparing(
|
.sorted(Comparator.comparing(
|
||||||
storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
|
storeCategoryWrapper -> storeCategoryWrapper.nameProperty().getValue().toLowerCase(Locale.ROOT)));
|
||||||
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
|
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
|
||||||
|
|
||||||
var emptyBinding = Bindings.isEmpty(category.getChildren());
|
var emptyBinding = Bindings.isEmpty(category.getChildren());
|
||||||
var v = new VerticalComp(List.of(categoryButton, children.hide(emptyBinding)));
|
var v = new VerticalComp(List.of(categoryButton, children.hide(emptyBinding)));
|
||||||
v.styleClass("category");
|
v.styleClass("category");
|
||||||
v.apply(struc -> {
|
v.apply(struc -> {
|
||||||
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
|
StoreViewState.get().getActiveCategory().subscribe(val -> {
|
||||||
struc.get().pseudoClassStateChanged(SELECTED, val.equals(category));
|
struc.get().pseudoClassStateChanged(SELECTED, val.equals(category));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -37,7 +36,7 @@ public class SvgView {
|
||||||
public static SvgView create(ObservableValue<String> content) {
|
public static SvgView create(ObservableValue<String> content) {
|
||||||
var widthProperty = new SimpleIntegerProperty();
|
var widthProperty = new SimpleIntegerProperty();
|
||||||
var heightProperty = new SimpleIntegerProperty();
|
var heightProperty = new SimpleIntegerProperty();
|
||||||
SimpleChangeListener.apply(content, val -> {
|
content.subscribe(val -> {
|
||||||
if (val == null || val.isBlank()) {
|
if (val == null || val.isBlank()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +68,7 @@ public class SvgView {
|
||||||
wv.setDisable(true);
|
wv.setDisable(true);
|
||||||
|
|
||||||
wv.getEngine().loadContent(svgContent.getValue() != null ? getHtml(svgContent.getValue()) : null);
|
wv.getEngine().loadContent(svgContent.getValue() != null ? getHtml(svgContent.getValue()) : null);
|
||||||
SimpleChangeListener.apply(svgContent, n -> {
|
svgContent.subscribe( n -> {
|
||||||
if (n == null) {
|
if (n == null) {
|
||||||
wv.setOpacity(0.0);
|
wv.setOpacity(0.0);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -28,7 +27,7 @@ public class TextAreaComp extends Comp<TextAreaComp.Structure> {
|
||||||
this.lastAppliedValue = value;
|
this.lastAppliedValue = value;
|
||||||
this.currentValue = new SimpleStringProperty(value.getValue());
|
this.currentValue = new SimpleStringProperty(value.getValue());
|
||||||
this.lazy = lazy;
|
this.lazy = lazy;
|
||||||
SimpleChangeListener.apply(value, val -> {
|
value.subscribe(val -> {
|
||||||
this.currentValue.setValue(val);
|
this.currentValue.setValue(val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
@ -27,7 +26,7 @@ public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||||
this.currentValue = new SimpleStringProperty(value.getValue());
|
this.currentValue = new SimpleStringProperty(value.getValue());
|
||||||
this.lazy = lazy;
|
this.lazy = lazy;
|
||||||
if (!lazy) {
|
if (!lazy) {
|
||||||
SimpleChangeListener.apply(currentValue, val -> {
|
currentValue.subscribe(val -> {
|
||||||
value.setValue(val);
|
value.setValue(val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
|
@ -29,7 +28,7 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
||||||
var box = new HBox();
|
var box = new HBox();
|
||||||
box.getStyleClass().add("toggle-group-comp");
|
box.getStyleClass().add("toggle-group-comp");
|
||||||
ToggleGroup group = new ToggleGroup();
|
ToggleGroup group = new ToggleGroup();
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(range), val -> {
|
PlatformThread.sync(range).subscribe(val -> {
|
||||||
if (!val.containsKey(value.getValue())) {
|
if (!val.containsKey(value.getValue())) {
|
||||||
this.value.setValue(null);
|
this.value.setValue(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,19 @@ import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.Augment;
|
import io.xpipe.app.fxcomps.augment.Augment;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
|
|
||||||
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||||
|
|
||||||
private final ObservableValue<String> text;
|
private final ObservableValue<String> text;
|
||||||
|
|
||||||
public FancyTooltipAugment(ObservableValue<String> text) {
|
public TooltipAugment(ObservableValue<String> text) {
|
||||||
this.text = PlatformThread.sync(text);
|
this.text = PlatformThread.sync(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FancyTooltipAugment(String key) {
|
public TooltipAugment(String key) {
|
||||||
this.text = AppI18n.observable(key);
|
this.text = AppI18n.observable(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +25,15 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
||||||
public void augment(S struc) {
|
public void augment(S struc) {
|
||||||
var region = struc.get();
|
var region = struc.get();
|
||||||
var tt = new Tooltip();
|
var tt = new Tooltip();
|
||||||
var toDisplay = text.getValue();
|
|
||||||
if (Shortcuts.getDisplayShortcut(region) != null) {
|
if (Shortcuts.getDisplayShortcut(region) != null) {
|
||||||
toDisplay = toDisplay + "\n\nShortcut: " + Shortcuts.getDisplayShortcut(region).getDisplayText();
|
var s = AppI18n.observable("shortcut");
|
||||||
|
var binding = Bindings.createStringBinding(() -> {
|
||||||
|
return text.getValue() + "\n\n" + s.getValue() + ": " + Shortcuts.getDisplayShortcut(region).getDisplayText();
|
||||||
|
}, text, s);
|
||||||
|
tt.textProperty().bind(binding);
|
||||||
|
} else {
|
||||||
|
tt.textProperty().bind(text);
|
||||||
}
|
}
|
||||||
tt.textProperty().setValue(toDisplay);
|
|
||||||
tt.setStyle("-fx-font-size: 11pt;");
|
tt.setStyle("-fx-font-size: 11pt;");
|
||||||
tt.setWrapText(true);
|
tt.setWrapText(true);
|
||||||
tt.setMaxWidth(400);
|
tt.setMaxWidth(400);
|
|
@ -1,98 +1,50 @@
|
||||||
package io.xpipe.app.fxcomps.util;
|
package io.xpipe.app.fxcomps.util;
|
||||||
|
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ListBinding;
|
|
||||||
import javafx.beans.property.Property;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.*;
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
@SuppressWarnings("InfiniteLoopStatement")
|
@SuppressWarnings("InfiniteLoopStatement")
|
||||||
public class BindingsHelper {
|
public class BindingsHelper {
|
||||||
|
|
||||||
private static final Set<ReferenceEntry> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
private static final Set<ReferenceEntry> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
/*
|
|
||||||
TODO: Proper cleanup. Maybe with a separate thread?
|
|
||||||
*/
|
|
||||||
private static final Map<WeakReference<Object>, Set<javafx.beans.Observable>> BINDINGS = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ThreadHelper.createPlatformThread("referenceGC", true, () -> {
|
ThreadHelper.createPlatformThread("referenceGC", true, () -> {
|
||||||
while (true) {
|
while (true) {
|
||||||
for (ReferenceEntry reference : REFERENCES) {
|
for (ReferenceEntry reference : REFERENCES) {
|
||||||
if (reference.canGc()) {
|
if (reference.canGc()) {
|
||||||
/*
|
REFERENCES.remove(reference);
|
||||||
TODO: Figure out why some bindings are garbage collected, even if they shouldn't
|
|
||||||
*/
|
|
||||||
// REFERENCES.remove(reference);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ThreadHelper.sleep(1000);
|
ThreadHelper.sleep(1000);
|
||||||
|
|
||||||
|
// Use for testing
|
||||||
|
// System.gc();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, V> void bindExclusive(
|
public static void preserve(Object source, Object target) {
|
||||||
Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
|
|
||||||
selected.addListener((c, o, n) -> {
|
|
||||||
toBind.unbind();
|
|
||||||
toBind.bind(map.get(n));
|
|
||||||
});
|
|
||||||
|
|
||||||
toBind.bind(map.get(selected.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void linkPersistently(Object source, Object target) {
|
|
||||||
REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target));
|
REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends Binding<?>> T persist(T binding) {
|
|
||||||
var dependencies = new HashSet<javafx.beans.Observable>();
|
|
||||||
while (dependencies.addAll(binding.getDependencies().stream()
|
|
||||||
.map(o -> (javafx.beans.Observable) o)
|
|
||||||
.toList())) {}
|
|
||||||
dependencies.add(binding);
|
|
||||||
BINDINGS.put(new WeakReference<>(binding), dependencies);
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends ListBinding<?>> T persist(T binding) {
|
|
||||||
var dependencies = new HashSet<javafx.beans.Observable>();
|
|
||||||
while (dependencies.addAll(binding.getDependencies().stream()
|
|
||||||
.map(o -> (javafx.beans.Observable) o)
|
|
||||||
.toList())) {}
|
|
||||||
dependencies.add(binding);
|
|
||||||
BINDINGS.put(new WeakReference<>(binding), dependencies);
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
|
|
||||||
setContent(l1, l2);
|
|
||||||
l2.addListener((ListChangeListener<? super T>) c -> {
|
|
||||||
setContent(l1, l2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, U> ObservableValue<U> map(
|
public static <T, U> ObservableValue<U> map(
|
||||||
ObservableValue<T> observableValue, Function<? super T, ? extends U> mapper) {
|
ObservableValue<T> observableValue, Function<? super T, ? extends U> mapper) {
|
||||||
return persist(Bindings.createObjectBinding(
|
return Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return mapper.apply(observableValue.getValue());
|
return mapper.apply(observableValue.getValue());
|
||||||
},
|
},
|
||||||
observableValue));
|
observableValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, U> ObservableValue<U> flatMap(
|
public static <T, U> ObservableValue<U> flatMap(
|
||||||
|
@ -105,229 +57,10 @@ public class BindingsHelper {
|
||||||
observableValue.addListener((observable, oldValue, newValue) -> {
|
observableValue.addListener((observable, oldValue, newValue) -> {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
});
|
});
|
||||||
linkPersistently(observableValue, prop);
|
preserve(prop, observableValue);
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
|
|
||||||
return BindingsHelper.persist(Bindings.createBooleanBinding(
|
|
||||||
() -> {
|
|
||||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
|
||||||
},
|
|
||||||
l.toArray(ObservableValue[]::new)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, V> void bindMappedContent(ObservableList<T> l1, ObservableList<V> l2, Function<V, T> map) {
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
setContent(l1, l2.stream().map(map).toList());
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
|
|
||||||
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
setContent(l1, l2.stream().map(map).toList());
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
linkPersistently(l2, l1);
|
|
||||||
return l1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, V> ObservableList<T> cachedMappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
|
|
||||||
var cache = new HashMap<V, T>();
|
|
||||||
|
|
||||||
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
cache.keySet().removeIf(t -> !l2.contains(t));
|
|
||||||
setContent(
|
|
||||||
l1,
|
|
||||||
l2.stream()
|
|
||||||
.map(v -> {
|
|
||||||
if (!cache.containsKey(v)) {
|
|
||||||
cache.put(v, map.apply(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.get(v);
|
|
||||||
})
|
|
||||||
.toList());
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
linkPersistently(l2, l1);
|
|
||||||
return l1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, V> ObservableList<T> cachedMappedContentBinding(
|
|
||||||
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
|
|
||||||
var cache = new HashMap<V, T>();
|
|
||||||
|
|
||||||
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
cache.keySet().removeIf(t -> !all.contains(t));
|
|
||||||
setContent(
|
|
||||||
l1,
|
|
||||||
shown.stream()
|
|
||||||
.map(v -> {
|
|
||||||
if (!cache.containsKey(v)) {
|
|
||||||
cache.put(v, map.apply(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.get(v);
|
|
||||||
})
|
|
||||||
.toList());
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
shown.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
linkPersistently(all, l1);
|
|
||||||
linkPersistently(shown, l1);
|
|
||||||
return l1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, U> ObservableValue<U> mappedBinding(
|
|
||||||
ObservableValue<T> observableValue, Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
|
|
||||||
var binding = (Binding<U>) observableValue.flatMap(mapper);
|
|
||||||
return persist(binding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <V> ObservableList<V> orderedContentBinding(
|
|
||||||
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
|
|
||||||
return orderedContentBinding(
|
|
||||||
l2,
|
|
||||||
Bindings.createObjectBinding(
|
|
||||||
() -> {
|
|
||||||
return new Comparator<>() {
|
|
||||||
@Override
|
|
||||||
public int compare(V o1, V o2) {
|
|
||||||
return comp.compare(o1, o2);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
observables));
|
|
||||||
}
|
|
||||||
|
|
||||||
// public static <T,U> ObservableValue<U> mappedBinding(ObservableValue<T> observableValue, Function<? super T, ?
|
|
||||||
// extends ObservableValue<? extends U>> mapper) {
|
|
||||||
// var v = new SimpleObjectProperty<U>();
|
|
||||||
// SimpleChangeListener.apply(observableValue, val -> {
|
|
||||||
// v.unbind();
|
|
||||||
// v.bind(mapper.apply(val));
|
|
||||||
// });
|
|
||||||
// return v;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public static <V> ObservableList<V> orderedContentBinding(
|
|
||||||
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
|
|
||||||
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
comp.addListener((observable, oldValue, newValue) -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
linkPersistently(l2, l1);
|
|
||||||
return l1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
|
|
||||||
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <V> ObservableList<V> filteredContentBinding(
|
|
||||||
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
|
|
||||||
return filteredContentBinding(
|
|
||||||
l2,
|
|
||||||
Bindings.createObjectBinding(
|
|
||||||
() -> {
|
|
||||||
return new Predicate<>() {
|
|
||||||
@Override
|
|
||||||
public boolean test(V v) {
|
|
||||||
return predicate.test(v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <V> ObservableList<V> filteredContentBinding(
|
|
||||||
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
|
|
||||||
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
|
||||||
Runnable runnable = () -> {
|
|
||||||
setContent(
|
|
||||||
l1,
|
|
||||||
predicate.getValue() != null
|
|
||||||
? l2.stream().filter(predicate.getValue()).toList()
|
|
||||||
: l2);
|
|
||||||
};
|
|
||||||
runnable.run();
|
|
||||||
l2.addListener((ListChangeListener<? super V>) c -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
predicate.addListener((c, o, n) -> {
|
|
||||||
runnable.run();
|
|
||||||
});
|
|
||||||
linkPersistently(l2, l1);
|
|
||||||
return l1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
|
|
||||||
if (target.equals(newList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.size() == 0) {
|
|
||||||
target.setAll(newList);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newList.size() == 0) {
|
|
||||||
target.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetSet = new HashSet<>(target);
|
|
||||||
var newSet = new HashSet<>(newList);
|
|
||||||
|
|
||||||
// Only add missing element
|
|
||||||
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
|
|
||||||
var l = new HashSet<>(newSet);
|
|
||||||
l.removeAll(targetSet);
|
|
||||||
if (l.size() > 0) {
|
|
||||||
var found = l.iterator().next();
|
|
||||||
var index = newList.indexOf(found);
|
|
||||||
target.add(index, found);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only remove not needed element
|
|
||||||
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
|
|
||||||
var l = new HashSet<>(targetSet);
|
|
||||||
l.removeAll(newSet);
|
|
||||||
if (l.size() > 0) {
|
|
||||||
target.remove(l.iterator().next());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other cases are more difficult
|
|
||||||
target.setAll(newList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
private static class ReferenceEntry {
|
private static class ReferenceEntry {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
package io.xpipe.app.fxcomps.util;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class ListBindingsHelper {
|
||||||
|
|
||||||
|
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
|
||||||
|
setContent(l1, l2);
|
||||||
|
l2.addListener((ListChangeListener<? super T>) c -> {
|
||||||
|
setContent(l1, l2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
|
||||||
|
return Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
||||||
|
},
|
||||||
|
l.toArray(ObservableValue[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
|
||||||
|
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
||||||
|
Runnable runnable = () -> {
|
||||||
|
setContent(l1, l2.stream().map(map).toList());
|
||||||
|
};
|
||||||
|
runnable.run();
|
||||||
|
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
BindingsHelper.preserve(l1, l2);
|
||||||
|
return l1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V> ObservableList<T> cachedMappedContentBinding(
|
||||||
|
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
|
||||||
|
var cache = new HashMap<V, T>();
|
||||||
|
|
||||||
|
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
|
||||||
|
Runnable runnable = () -> {
|
||||||
|
cache.keySet().removeIf(t -> !all.contains(t));
|
||||||
|
setContent(
|
||||||
|
l1,
|
||||||
|
shown.stream()
|
||||||
|
.map(v -> {
|
||||||
|
if (!cache.containsKey(v)) {
|
||||||
|
cache.put(v, map.apply(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get(v);
|
||||||
|
})
|
||||||
|
.toList());
|
||||||
|
};
|
||||||
|
runnable.run();
|
||||||
|
shown.addListener((ListChangeListener<? super V>) c -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
BindingsHelper.preserve(l1, all);
|
||||||
|
BindingsHelper.preserve(l1, shown);
|
||||||
|
return l1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> ObservableList<V> orderedContentBinding(
|
||||||
|
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
|
||||||
|
return orderedContentBinding(
|
||||||
|
l2,
|
||||||
|
Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
return new Comparator<>() {
|
||||||
|
@Override
|
||||||
|
public int compare(V o1, V o2) {
|
||||||
|
return comp.compare(o1, o2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
observables));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> ObservableList<V> orderedContentBinding(
|
||||||
|
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
|
||||||
|
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
||||||
|
Runnable runnable = () -> {
|
||||||
|
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
|
||||||
|
};
|
||||||
|
runnable.run();
|
||||||
|
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
comp.addListener((observable, oldValue, newValue) -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
BindingsHelper.preserve(l1, l2);
|
||||||
|
return l1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
|
||||||
|
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> ObservableList<V> filteredContentBinding(
|
||||||
|
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
|
||||||
|
return filteredContentBinding(
|
||||||
|
l2,
|
||||||
|
Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
return new Predicate<>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(V v) {
|
||||||
|
return predicate.test(v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> ObservableList<V> filteredContentBinding(
|
||||||
|
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
|
||||||
|
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
|
||||||
|
Runnable runnable = () -> {
|
||||||
|
setContent(
|
||||||
|
l1,
|
||||||
|
predicate.getValue() != null
|
||||||
|
? l2.stream().filter(predicate.getValue()).toList()
|
||||||
|
: l2);
|
||||||
|
};
|
||||||
|
runnable.run();
|
||||||
|
l2.addListener((ListChangeListener<? super V>) c -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
predicate.addListener((c, o, n) -> {
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
BindingsHelper.preserve(l1, l2);
|
||||||
|
return l1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
|
||||||
|
if (target.equals(newList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.size() == 0) {
|
||||||
|
target.setAll(newList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newList.size() == 0) {
|
||||||
|
target.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetSet = new HashSet<>(target);
|
||||||
|
var newSet = new HashSet<>(newList);
|
||||||
|
|
||||||
|
// Only add missing element
|
||||||
|
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
|
||||||
|
var l = new HashSet<>(newSet);
|
||||||
|
l.removeAll(targetSet);
|
||||||
|
if (l.size() > 0) {
|
||||||
|
var found = l.iterator().next();
|
||||||
|
var index = newList.indexOf(found);
|
||||||
|
target.add(index, found);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only remove not needed element
|
||||||
|
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
|
||||||
|
var l = new HashSet<>(targetSet);
|
||||||
|
l.removeAll(newSet);
|
||||||
|
if (l.size() > 0) {
|
||||||
|
target.remove(l.iterator().next());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other cases are more difficult
|
||||||
|
target.setAll(newList);
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,7 +85,6 @@ public class PlatformThread {
|
||||||
ov.removeListener(invListenerMap.getOrDefault(listener, listener));
|
ov.removeListener(invListenerMap.getOrDefault(listener, listener));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
BindingsHelper.linkPersistently(obs, ov);
|
|
||||||
return obs;
|
return obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +271,6 @@ public class PlatformThread {
|
||||||
ol.removeListener(invListenerMap.getOrDefault(listener, listener));
|
ol.removeListener(invListenerMap.getOrDefault(listener, listener));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
BindingsHelper.linkPersistently(obs, ol);
|
|
||||||
return obs;
|
return obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class Shortcuts {
|
||||||
|
|
||||||
DISPLAY_SHORTCUTS.put(region, comb);
|
DISPLAY_SHORTCUTS.put(region, comb);
|
||||||
AtomicReference<Scene> scene = new AtomicReference<>();
|
AtomicReference<Scene> scene = new AtomicReference<>();
|
||||||
SimpleChangeListener.apply(region.sceneProperty(), s -> {
|
region.sceneProperty().subscribe(s -> {
|
||||||
if (Objects.equals(s, scene.get())) {
|
if (Objects.equals(s, scene.get())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package io.xpipe.app.fxcomps.util;
|
|
||||||
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SimpleChangeListener<T> {
|
|
||||||
|
|
||||||
static <T> void apply(ObservableValue<T> obs, SimpleChangeListener<T> cl) {
|
|
||||||
obs.addListener(cl.wrapped());
|
|
||||||
cl.onChange(obs.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChange(T val);
|
|
||||||
|
|
||||||
default ChangeListener<T> wrapped() {
|
|
||||||
return (observable, oldValue, newValue) -> this.onChange(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,11 +49,13 @@ public class AppPrefs {
|
||||||
map(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class);
|
map(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class);
|
||||||
final ObjectProperty<ExternalTerminalType> terminalType =
|
final ObjectProperty<ExternalTerminalType> terminalType =
|
||||||
map(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class);
|
map(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class);
|
||||||
|
final ObjectProperty<ExternalRdpClientType> rdpClientType =
|
||||||
|
map(new SimpleObjectProperty<>(), "rdpClientType", ExternalRdpClientType.class);
|
||||||
final DoubleProperty windowOpacity = map(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class);
|
final DoubleProperty windowOpacity = map(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class);
|
||||||
|
final StringProperty customRdpClientCommand =
|
||||||
|
map(new SimpleStringProperty(null), "customRdpClientCommand", String.class);
|
||||||
final StringProperty customTerminalCommand =
|
final StringProperty customTerminalCommand =
|
||||||
map(new SimpleStringProperty(""), "customTerminalCommand", String.class);
|
map(new SimpleStringProperty(null), "customTerminalCommand", String.class);
|
||||||
final BooleanProperty preferTerminalTabs =
|
|
||||||
map(new SimpleBooleanProperty(true), "preferTerminalTabs", Boolean.class);
|
|
||||||
final BooleanProperty clearTerminalOnInit =
|
final BooleanProperty clearTerminalOnInit =
|
||||||
map(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class);
|
map(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class);
|
||||||
public final BooleanProperty disableCertutilUse =
|
public final BooleanProperty disableCertutilUse =
|
||||||
|
@ -104,7 +106,7 @@ public class AppPrefs {
|
||||||
map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class);
|
map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class);
|
||||||
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
||||||
bindDeveloperTrue(developerDisableGuiRestrictions);
|
bindDeveloperTrue(developerDisableGuiRestrictions);
|
||||||
private final ObjectProperty<SupportedLocale> language =
|
final ObjectProperty<SupportedLocale> language =
|
||||||
map(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), "language", SupportedLocale.class);
|
map(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), "language", SupportedLocale.class);
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -139,6 +141,7 @@ public class AppPrefs {
|
||||||
new AppearanceCategory(),
|
new AppearanceCategory(),
|
||||||
new TerminalCategory(),
|
new TerminalCategory(),
|
||||||
new EditorCategory(),
|
new EditorCategory(),
|
||||||
|
new RdpCategory(),
|
||||||
new SyncCategory(),
|
new SyncCategory(),
|
||||||
new VaultCategory(),
|
new VaultCategory(),
|
||||||
new LocalShellCategory(),
|
new LocalShellCategory(),
|
||||||
|
@ -357,10 +360,18 @@ public class AppPrefs {
|
||||||
return terminalType;
|
return terminalType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<ExternalRdpClientType> rdpClientType() {
|
||||||
|
return rdpClientType;
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableValue<String> customTerminalCommand() {
|
public ObservableValue<String> customTerminalCommand() {
|
||||||
return customTerminalCommand;
|
return customTerminalCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<String> customRdpClientCommand() {
|
||||||
|
return customRdpClientCommand;
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableValue<Path> storageDirectory() {
|
public ObservableValue<Path> storageDirectory() {
|
||||||
return storageDirectory;
|
return storageDirectory;
|
||||||
}
|
}
|
||||||
|
@ -411,7 +422,12 @@ public class AppPrefs {
|
||||||
if (externalEditor.get() == null) {
|
if (externalEditor.get() == null) {
|
||||||
ExternalEditorType.detectDefault();
|
ExternalEditorType.detectDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
terminalType.set(ExternalTerminalType.determineDefault(terminalType.get()));
|
terminalType.set(ExternalTerminalType.determineDefault(terminalType.get()));
|
||||||
|
|
||||||
|
if (rdpClientType.get() == null) {
|
||||||
|
rdpClientType.setValue(ExternalRdpClientType.determineDefault());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<?> getCustomComp(String id) {
|
public Comp<?> getCustomComp(String id) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.prefs;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
|
@ -28,7 +27,7 @@ public class AppPrefsComp extends SimpleComp {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}));
|
}));
|
||||||
var pfxSp = new ScrollPane();
|
var pfxSp = new ScrollPane();
|
||||||
SimpleChangeListener.apply(AppPrefs.get().getSelectedCategory(), val -> {
|
AppPrefs.get().getSelectedCategory().subscribe(val -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
pfxSp.setContent(map.get(val));
|
pfxSp.setContent(map.get(val));
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -27,7 +26,7 @@ public class AppPrefsSidebarComp extends SimpleComp {
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get().setTextAlignment(TextAlignment.LEFT);
|
struc.get().setTextAlignment(TextAlignment.LEFT);
|
||||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||||
SimpleChangeListener.apply(AppPrefs.get().getSelectedCategory(), val -> {
|
AppPrefs.get().getSelectedCategory().subscribe(val -> {
|
||||||
struc.get().pseudoClassStateChanged(SELECTED, appPrefsCategory.equals(val));
|
struc.get().pseudoClassStateChanged(SELECTED, appPrefsCategory.equals(val));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -36,13 +35,15 @@ public class AppPrefsSidebarComp extends SimpleComp {
|
||||||
.toList();
|
.toList();
|
||||||
var vbox = new VerticalComp(buttons).styleClass("sidebar");
|
var vbox = new VerticalComp(buttons).styleClass("sidebar");
|
||||||
vbox.apply(struc -> {
|
vbox.apply(struc -> {
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(AppPrefs.get().getSelectedCategory()), val -> {
|
AppPrefs.get().getSelectedCategory().subscribe(val -> {
|
||||||
var index = val != null ? AppPrefs.get().getCategories().indexOf(val) : 0;
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
if (index >= struc.get().getChildren().size()) {
|
var index = val != null ? AppPrefs.get().getCategories().indexOf(val) : 0;
|
||||||
return;
|
if (index >= struc.get().getChildren().size()) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
((Button) struc.get().getChildren().get(index)).fire();
|
((Button) struc.get().getChildren().get(index)).fire();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return vbox.createRegion();
|
return vbox.createRegion();
|
||||||
|
|
|
@ -2,12 +2,21 @@ package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import atlantafx.base.controls.ProgressSliderSkin;
|
import atlantafx.base.controls.ProgressSliderSkin;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppTheme;
|
import io.xpipe.app.core.AppTheme;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IntFieldComp;
|
import io.xpipe.app.fxcomps.impl.IntFieldComp;
|
||||||
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
import io.xpipe.app.util.OptionsBuilder;
|
import io.xpipe.app.util.OptionsBuilder;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class AppearanceCategory extends AppPrefsCategory {
|
public class AppearanceCategory extends AppPrefsCategory {
|
||||||
|
|
||||||
|
@ -16,12 +25,28 @@ public class AppearanceCategory extends AppPrefsCategory {
|
||||||
return "appearance";
|
return "appearance";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comp<?> languageChoice() {
|
||||||
|
var prefs = AppPrefs.get();
|
||||||
|
var c = ChoiceComp.ofTranslatable(prefs.language, Arrays.asList(SupportedLocale.values()), false);
|
||||||
|
var visit = new ButtonComp(AppI18n.observable("translate"), new FontIcon("mdi2w-web"), () -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.TRANSLATE);
|
||||||
|
});
|
||||||
|
return new HorizontalComp(List.of(c, visit)).apply(struc -> {
|
||||||
|
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||||
|
struc.get().setSpacing(10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Comp<?> create() {
|
protected Comp<?> create() {
|
||||||
var prefs = AppPrefs.get();
|
var prefs = AppPrefs.get();
|
||||||
return new OptionsBuilder()
|
return new OptionsBuilder()
|
||||||
.addTitle("uiOptions")
|
.addTitle("uiOptions")
|
||||||
.sub(new OptionsBuilder()
|
.sub(new OptionsBuilder()
|
||||||
|
.nameAndDescription("language")
|
||||||
|
.addComp(
|
||||||
|
languageChoice(),
|
||||||
|
prefs.language)
|
||||||
.nameAndDescription("theme")
|
.nameAndDescription("theme")
|
||||||
.addComp(
|
.addComp(
|
||||||
ChoiceComp.ofTranslatable(prefs.theme, AppTheme.Theme.ALL, false)
|
ChoiceComp.ofTranslatable(prefs.theme, AppTheme.Theme.ALL, false)
|
||||||
|
|
163
app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java
Normal file
163
app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.util.*;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public interface ExternalRdpClientType extends PrefsChoiceValue {
|
||||||
|
|
||||||
|
@Value
|
||||||
|
class LaunchConfiguration {
|
||||||
|
String title;
|
||||||
|
RdpConfig config;
|
||||||
|
UUID storeId;
|
||||||
|
SecretRetrievalStrategy password;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalRdpClientType {
|
||||||
|
|
||||||
|
public PathCheckType(String id, String executable, boolean explicityAsync) {
|
||||||
|
super(id, executable, explicityAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalRdpClientType {
|
||||||
|
|
||||||
|
public MacOsType(String id, String applicationName) {
|
||||||
|
super(id, applicationName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void launch(LaunchConfiguration configuration) throws Exception;
|
||||||
|
|
||||||
|
default Path writeConfig(RdpConfig input) throws Exception {
|
||||||
|
var file = LocalShell.getShell().getSystemTemporaryDirectory().join("exec-" + ScriptHelper.getScriptId() + ".rdp");
|
||||||
|
var string = input.toString();
|
||||||
|
Files.writeString(file.toLocalPath(), string);
|
||||||
|
return file.toLocalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalRdpClientType MSTSC = new PathCheckType("app.mstsc", "mstsc.exe", true) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
|
var adaptedRdpConfig = getAdaptedConfig(configuration);
|
||||||
|
var file = writeConfig(adaptedRdpConfig);
|
||||||
|
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception {
|
||||||
|
var input = configuration.getConfig();
|
||||||
|
if (input.get("password 51").isPresent()) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = input.get("full address")
|
||||||
|
.map(typedValue -> typedValue.getValue())
|
||||||
|
.orElse("?");
|
||||||
|
var pass = SecretManager.retrieve(
|
||||||
|
configuration.getPassword(), "Password for " + address, configuration.getStoreId(), 0);
|
||||||
|
if (pass == null) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapted = input.overlay(Map.of(
|
||||||
|
"password 51",
|
||||||
|
new RdpConfig.TypedValue("b", encrypt(pass.getSecretValue())),
|
||||||
|
"prompt for credentials",
|
||||||
|
new RdpConfig.TypedValue("i", "0")));
|
||||||
|
return adapted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encrypt(String password) throws Exception {
|
||||||
|
var ps = LocalShell.getLocalPowershell();
|
||||||
|
var cmd = ps.command(
|
||||||
|
"(\"" + password + "\" | ConvertTo-SecureString -AsPlainText -Force) | ConvertFrom-SecureString;");
|
||||||
|
cmd.setSensitive();
|
||||||
|
return cmd.readStdoutOrThrow();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExternalRdpClientType REMMINA = new PathCheckType("app.remmina", "remmina", true) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
|
var file = writeConfig(configuration.getConfig());
|
||||||
|
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().add(executable).add("-c").addFile(file.toString()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExternalRdpClientType MICROSOFT_REMOTE_DESKTOP_MACOS_APP = new MacOsType("app.microsoftRemoteDesktopApp", "Microsoft Remote Desktop.app") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
|
var file = writeConfig(configuration.getConfig());
|
||||||
|
LocalShell.getShell()
|
||||||
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
|
.add("open", "-a")
|
||||||
|
.addQuoted("Microsoft Remote Desktop.app")
|
||||||
|
.addFile(file.toString()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CustomType extends ExternalApplicationType implements ExternalRdpClientType {
|
||||||
|
|
||||||
|
public CustomType() {
|
||||||
|
super("app.custom");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||||
|
var customCommand = AppPrefs.get().customRdpClientCommand().getValue();
|
||||||
|
if (customCommand == null || customCommand.isBlank()) {
|
||||||
|
throw ErrorEvent.expected(new IllegalStateException("No custom RDP command specified"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||||
|
ExternalApplicationHelper.startAsync(CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", writeConfig(configuration.getConfig()).toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalRdpClientType CUSTOM = new CustomType();
|
||||||
|
|
||||||
|
List<ExternalRdpClientType> WINDOWS_CLIENTS = List.of(MSTSC);
|
||||||
|
List<ExternalRdpClientType> LINUX_CLIENTS = List.of(REMMINA);
|
||||||
|
List<ExternalRdpClientType> MACOS_CLIENTS = List.of(MICROSOFT_REMOTE_DESKTOP_MACOS_APP);
|
||||||
|
|
||||||
|
@SuppressWarnings("TrivialFunctionalExpressionUsage")
|
||||||
|
List<ExternalRdpClientType> ALL = ((Supplier<List<ExternalRdpClientType>>) () -> {
|
||||||
|
var all = new ArrayList<ExternalRdpClientType>();
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
all.addAll(WINDOWS_CLIENTS);
|
||||||
|
}
|
||||||
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
all.addAll(LINUX_CLIENTS);
|
||||||
|
}
|
||||||
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
|
all.addAll(MACOS_CLIENTS);
|
||||||
|
}
|
||||||
|
all.add(CUSTOM);
|
||||||
|
return all;
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
static ExternalRdpClientType determineDefault() {
|
||||||
|
return ALL.stream()
|
||||||
|
.filter(t -> !t.equals(CUSTOM))
|
||||||
|
.filter(t -> t.isAvailable())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
32
app/src/main/java/io/xpipe/app/prefs/RdpCategory.java
Normal file
32
app/src/main/java/io/xpipe/app/prefs/RdpCategory.java
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||||
|
import io.xpipe.app.util.OptionsBuilder;
|
||||||
|
|
||||||
|
public class RdpCategory extends AppPrefsCategory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getId() {
|
||||||
|
return "rdp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> create() {
|
||||||
|
var prefs = AppPrefs.get();
|
||||||
|
return new OptionsBuilder()
|
||||||
|
.addTitle("rdpConfiguration")
|
||||||
|
.sub(new OptionsBuilder()
|
||||||
|
.nameAndDescription("rdpClient")
|
||||||
|
.addComp(ChoiceComp.ofTranslatable(
|
||||||
|
prefs.rdpClientType, PrefsChoiceValue.getSupported(ExternalRdpClientType.class), false))
|
||||||
|
.nameAndDescription("customRdpClientCommand")
|
||||||
|
.addComp(new TextFieldComp(prefs.customRdpClientCommand, true)
|
||||||
|
.apply(struc -> struc.get().setPromptText("myrdpclient -c $FILE"))
|
||||||
|
.hide(prefs.rdpClientType.isNotEqualTo(ExternalRdpClientType.CUSTOM)))
|
||||||
|
)
|
||||||
|
.buildComp();
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ import java.util.Locale;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum SupportedLocale implements PrefsChoiceValue {
|
public enum SupportedLocale implements PrefsChoiceValue {
|
||||||
ENGLISH(Locale.ENGLISH, "english");
|
ENGLISH(Locale.ENGLISH, "english"),
|
||||||
// GERMAN(Locale.GERMAN, "german");
|
GERMAN(Locale.GERMAN, "german");
|
||||||
|
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
|
@ -8,7 +8,6 @@ import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
import io.xpipe.app.util.OptionsBuilder;
|
import io.xpipe.app.util.OptionsBuilder;
|
||||||
|
@ -66,14 +65,14 @@ public class TerminalCategory extends AppPrefsCategory {
|
||||||
|
|
||||||
Hyperlinks.open(t.getWebsite());
|
Hyperlinks.open(t.getWebsite());
|
||||||
});
|
});
|
||||||
var visitVisible = BindingsHelper.persist(Bindings.createBooleanBinding(() -> {
|
var visitVisible = Bindings.createBooleanBinding(() -> {
|
||||||
var t = prefs.terminalType().getValue();
|
var t = prefs.terminalType().getValue();
|
||||||
if (t == null || t.getWebsite() == null) {
|
if (t == null || t.getWebsite() == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, prefs.terminalType()));
|
}, prefs.terminalType());
|
||||||
visit.visible(visitVisible);
|
visit.visible(visitVisible);
|
||||||
|
|
||||||
return new HorizontalComp(List.of(c, visit)).apply(struc -> {
|
return new HorizontalComp(List.of(c, visit)).apply(struc -> {
|
||||||
|
@ -108,22 +107,6 @@ public class TerminalCategory extends AppPrefsCategory {
|
||||||
.apply(struc -> struc.get().setPromptText("myterminal -e $CMD"))
|
.apply(struc -> struc.get().setPromptText("myterminal -e $CMD"))
|
||||||
.hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM)))
|
.hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM)))
|
||||||
.addComp(terminalTest)
|
.addComp(terminalTest)
|
||||||
.name("preferTerminalTabs")
|
|
||||||
.description(Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
var disabled = prefs.terminalType().getValue() != null
|
|
||||||
&& !prefs.terminalType.get().supportsTabs();
|
|
||||||
return !disabled
|
|
||||||
? AppI18n.get("preferTerminalTabs")
|
|
||||||
: AppI18n.get(
|
|
||||||
"preferTerminalTabsDisabled",
|
|
||||||
prefs.terminalType()
|
|
||||||
.getValue()
|
|
||||||
.toTranslatedString()
|
|
||||||
.getValue());
|
|
||||||
},
|
|
||||||
prefs.terminalType()))
|
|
||||||
.addToggle(prefs.preferTerminalTabs)
|
|
||||||
.disable(Bindings.createBooleanBinding(
|
.disable(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return prefs.terminalType().getValue() != null
|
return prefs.terminalType().getValue() != null
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate()));
|
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restart() {
|
private void performUpdateAndRestart() {
|
||||||
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
||||||
UpdateAvailableAlert.showIfNeeded();
|
UpdateAvailableAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
return new TileButtonComp(name, description, graphic, actionEvent -> {
|
return new TileButtonComp(name, description, graphic, actionEvent -> {
|
||||||
actionEvent.consume();
|
actionEvent.consume();
|
||||||
if (updateReady.getValue()) {
|
if (updateReady.getValue()) {
|
||||||
restart();
|
performUpdateAndRestart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,12 @@ public class UpdateAvailableAlert {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check whether we still have the latest version prepared
|
||||||
|
uh.refreshUpdateCheckSilent();
|
||||||
|
if (uh.getPreparedUpdate().getValue() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var u = uh.getPreparedUpdate().getValue();
|
var u = uh.getPreparedUpdate().getValue();
|
||||||
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
alert.setTitle(AppI18n.get("updateReadyAlertTitle"));
|
alert.setTitle(AppI18n.get("updateReadyAlertTitle"));
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
package io.xpipe.app.update;
|
|
||||||
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.beacon.XPipeInstance;
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class XPipeInstanceHelper {
|
|
||||||
|
|
||||||
public static UUID getInstanceId() {
|
|
||||||
var file = AppProperties.get().getDataDir().resolve("instance");
|
|
||||||
if (!Files.exists(file)) {
|
|
||||||
var id = UUID.randomUUID();
|
|
||||||
try {
|
|
||||||
Files.writeString(file, id.toString());
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return UUID.fromString(Files.readString(file));
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
|
||||||
return UUID.randomUUID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSupported(ShellStore host) {
|
|
||||||
try (var pc = host.control().start();
|
|
||||||
var cmd = pc.command("xpipe")) {
|
|
||||||
cmd.discardOrThrow();
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<XPipeInstance> getInstance(ShellStore store) {
|
|
||||||
if (!isSupported(store)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
// try (BeaconClient beaconClient = ProcessBeaconClient.create(store)) {
|
|
||||||
// beaconClient.sendRequest(InstanceExchange.Request.builder().build());
|
|
||||||
// InstanceExchange.Response response = beaconClient.receiveResponse();
|
|
||||||
// return Optional.of(response.getInstance());
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// return Optional.empty();
|
|
||||||
// }
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static XPipeInstance refresh() {
|
|
||||||
Map<ShellStore, Optional<XPipeInstance>> map = DataStorage.get().getStoreEntries().stream()
|
|
||||||
.filter(entry -> entry.getStore() instanceof ShellStore)
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
entry -> entry.getStore().asNeeded(),
|
|
||||||
entry -> getInstance(entry.getStore().asNeeded())));
|
|
||||||
var adjacent = map.entrySet().stream()
|
|
||||||
.filter(shellStoreOptionalEntry ->
|
|
||||||
shellStoreOptionalEntry.getValue().isPresent())
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
entry -> entry.getKey(), entry -> entry.getValue().get()));
|
|
||||||
var reachable = adjacent.values().stream()
|
|
||||||
.map(XPipeInstance::getReachable)
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
var id = getInstanceId();
|
|
||||||
var name = "test";
|
|
||||||
return new XPipeInstance(id, name, adjacent, reachable);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.comp.store.StoreCategoryWrapper;
|
||||||
import io.xpipe.app.comp.store.StoreViewState;
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
|
@ -28,7 +27,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
SimpleChangeListener.apply(external, newValue -> {
|
external.subscribe(newValue -> {
|
||||||
if (newValue == null) {
|
if (newValue == null) {
|
||||||
value.setValue(root);
|
value.setValue(root);
|
||||||
} else if (root == null) {
|
} else if (root == null) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ public class Hyperlinks {
|
||||||
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
|
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
|
||||||
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
|
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
|
||||||
public static final String SECURITY = "https://docs.xpipe.io/security";
|
public static final String SECURITY = "https://docs.xpipe.io/security";
|
||||||
|
public static final String TRANSLATE = "https://github.com/xpipe-io/xpipe/lang";
|
||||||
public static final String DISCORD = "https://discord.gg/8y89vS8cRb";
|
public static final String DISCORD = "https://discord.gg/8y89vS8cRb";
|
||||||
public static final String SLACK =
|
public static final String SLACK =
|
||||||
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
|
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
|
||||||
|
|
|
@ -297,7 +297,7 @@ public class OptionsBuilder {
|
||||||
|
|
||||||
public OptionsBuilder longDescription(String descriptionKey) {
|
public OptionsBuilder longDescription(String descriptionKey) {
|
||||||
finishCurrent();
|
finishCurrent();
|
||||||
longDescription = AppI18n.getInstance().getMarkdownDocumentation(descriptionKey);
|
longDescription = AppI18n.get().getMarkdownDocumentation(descriptionKey);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
64
app/src/main/java/io/xpipe/app/util/RdpConfig.java
Normal file
64
app/src/main/java/io/xpipe/app/util/RdpConfig.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class RdpConfig {
|
||||||
|
|
||||||
|
public static RdpConfig parseFile(String file) throws IOException {
|
||||||
|
var content = Files.readString(Path.of(file));
|
||||||
|
return parseContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RdpConfig parseContent(String content) {
|
||||||
|
var map = new LinkedHashMap<String, TypedValue>();
|
||||||
|
content.lines().forEach(s -> {
|
||||||
|
var split = s.split(":");
|
||||||
|
if (split.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split.length == 2) {
|
||||||
|
map.put(split[0].trim(), new RdpConfig.TypedValue("s", split[1].trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (split.length == 3) {
|
||||||
|
map.put(split[0].trim(), new RdpConfig.TypedValue(split[1].trim(), split[2].trim()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new RdpConfig(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class TypedValue {
|
||||||
|
String type;
|
||||||
|
String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, TypedValue> content;
|
||||||
|
|
||||||
|
public RdpConfig overlay(Map<String, TypedValue> override) {
|
||||||
|
var newMap = new LinkedHashMap<>(content);
|
||||||
|
newMap.putAll(override);
|
||||||
|
return new RdpConfig(newMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return content.entrySet().stream().map(e -> {
|
||||||
|
return e.getKey() + ":" + e.getValue().getType() + ":" + e.getValue().getValue();
|
||||||
|
}).collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TypedValue> get(String key) {
|
||||||
|
return Optional.ofNullable(content.get(key));
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.ScanProvider;
|
import io.xpipe.app.ext.ScanProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
|
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
@ -168,7 +167,7 @@ public class ScanAlert {
|
||||||
})
|
})
|
||||||
.padding(new Insets(20));
|
.padding(new Insets(20));
|
||||||
|
|
||||||
SimpleChangeListener.apply(entry, newValue -> {
|
entry.subscribe(newValue -> {
|
||||||
selected.clear();
|
selected.clear();
|
||||||
stackPane.getChildren().clear();
|
stackPane.getChildren().clear();
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ open module io.xpipe.app {
|
||||||
requires jdk.management;
|
requires jdk.management;
|
||||||
requires jdk.management.agent;
|
requires jdk.management.agent;
|
||||||
requires net.steppschuh.markdowngenerator;
|
requires net.steppschuh.markdowngenerator;
|
||||||
|
requires com.shinyhut.vernacular;
|
||||||
|
|
||||||
// Required by extensions
|
// Required by extensions
|
||||||
requires java.security.jgss;
|
requires java.security.jgss;
|
||||||
|
@ -150,6 +151,5 @@ open module io.xpipe.app {
|
||||||
TerminalWaitExchangeImpl,
|
TerminalWaitExchangeImpl,
|
||||||
TerminalLaunchExchangeImpl,
|
TerminalLaunchExchangeImpl,
|
||||||
QueryStoreExchangeImpl,
|
QueryStoreExchangeImpl,
|
||||||
InstanceExchangeImpl,
|
|
||||||
VersionExchangeImpl;
|
VersionExchangeImpl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ browseInternalStorage=Browse internal storage
|
||||||
addTunnel=Tunnel ...
|
addTunnel=Tunnel ...
|
||||||
addScript=Script ...
|
addScript=Script ...
|
||||||
addHost=Remote Host ...
|
addHost=Remote Host ...
|
||||||
|
addVisual=Visual ...
|
||||||
addShell=Shell Environment ...
|
addShell=Shell Environment ...
|
||||||
addCommand=Custom Command ...
|
addCommand=Custom Command ...
|
||||||
addAutomatically=Search Automatically ...
|
addAutomatically=Search Automatically ...
|
||||||
|
@ -111,8 +112,6 @@ newLine=Newline
|
||||||
crlf=CRLF (Windows)
|
crlf=CRLF (Windows)
|
||||||
lf=LF (Linux)
|
lf=LF (Linux)
|
||||||
none=None
|
none=None
|
||||||
expand=Expand
|
|
||||||
accessSubConnections=Access sub connections
|
|
||||||
common=Common
|
common=Common
|
||||||
key=Key
|
key=Key
|
||||||
color=Color
|
color=Color
|
||||||
|
@ -140,7 +139,7 @@ test=Test
|
||||||
lockCreationAlertTitle=Set passphrase
|
lockCreationAlertTitle=Set passphrase
|
||||||
lockCreationAlertHeader=Set your new master passphrase
|
lockCreationAlertHeader=Set your new master passphrase
|
||||||
finish=Finish
|
finish=Finish
|
||||||
error=An error occurred
|
error=Error
|
||||||
downloadStageDescription=Downloads files to your local machine, so you can drag and drop them into your native desktop environment.
|
downloadStageDescription=Downloads files to your local machine, so you can drag and drop them into your native desktop environment.
|
||||||
ok=Ok
|
ok=Ok
|
||||||
search=Search
|
search=Search
|
||||||
|
|
|
@ -43,11 +43,6 @@
|
||||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, rgb(12, 11, 11) 40%, rgb(32, 32, 40) 50%, rgb(35, 29, 29) 100%);
|
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, rgb(12, 11, 11) 40%, rgb(32, 32, 40) 50%, rgb(35, 29, 29) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-header-bar .menu-button:hover, .root:key-navigation .store-header-bar .menu-button:focused {
|
|
||||||
-fx-background-color: -color-bg-default;
|
|
||||||
-fx-border-color: -color-fg-default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-header-bar .menu-button > * {
|
.store-header-bar .menu-button > * {
|
||||||
-fx-text-fill: -color-bg-default;
|
-fx-text-fill: -color-bg-default;
|
||||||
}
|
}
|
||||||
|
@ -61,15 +56,20 @@
|
||||||
-fx-border-width: 4;
|
-fx-border-width: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-header-bar .menu-button:hover > *, .root:key-navigation .store-header-bar .menu-button:focused > * {
|
.root .store-header-bar .menu-button:hover, .root:key-navigation .store-header-bar .menu-button:focused {
|
||||||
|
-fx-background-color: -color-bg-default;
|
||||||
|
-fx-border-color: -color-fg-default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root .store-header-bar .menu-button:hover > *, .root:key-navigation .store-header-bar .menu-button:focused > * {
|
||||||
-fx-text-fill: -color-fg-default;
|
-fx-text-fill: -color-fg-default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-header-bar .menu-button:hover > * > .ikonli-font-icon, .root:key-navigation .store-header-bar .menu-button:focused > * > .ikonli-font-icon {
|
.root .store-header-bar .menu-button:hover > * > .ikonli-font-icon, .root:key-navigation .store-header-bar .menu-button:focused > * > .ikonli-font-icon {
|
||||||
-fx-icon-color: -color-fg-default;
|
-fx-icon-color: -color-fg-default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-header-bar .menu-button:hover .arrow, .root:key-navigation .store-header-bar .menu-button:focused .arrow {
|
.root .store-header-bar .menu-button:hover .arrow, .root:key-navigation .store-header-bar .menu-button:focused .arrow {
|
||||||
-fx-border-color: -color-fg-default;
|
-fx-border-color: -color-fg-default;
|
||||||
-fx-border-width: 4;
|
-fx-border-width: 4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,11 @@
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root:pretty:dark.background {
|
.root:dark.background {
|
||||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, 5%) 40%, derive(-color-bg-default, 2%) 50%, derive(-color-bg-default, 5%) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.root:performance:dark.background {
|
|
||||||
-fx-background-color: derive(-color-bg-default, 5%);
|
-fx-background-color: derive(-color-bg-default, 5%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.root:pretty:light.background {
|
.root:light.background {
|
||||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, -9%) 40%, derive(-color-bg-default, 1%) 50%, derive(-color-bg-default, -9%) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.root:performance:light.background {
|
|
||||||
-fx-background-color: derive(-color-bg-default, -9%);
|
-fx-background-color: derive(-color-bg-default, -9%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,8 @@ import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import io.xpipe.beacon.exchange.MessageExchanges;
|
import io.xpipe.beacon.exchange.MessageExchanges;
|
||||||
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
||||||
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.util.Deobfuscator;
|
import io.xpipe.core.util.Deobfuscator;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.core.util.ProxyManagerProvider;
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -62,51 +60,6 @@ public class BeaconClient implements AutoCloseable {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeaconClient connectProxy(ShellStore proxy) throws Exception {
|
|
||||||
var control = proxy.control().start();
|
|
||||||
if (!ProxyManagerProvider.get().setup(control)) {
|
|
||||||
throw new IOException("XPipe connector required to perform operation");
|
|
||||||
}
|
|
||||||
var command = control.command("xpipe beacon --raw").start();
|
|
||||||
command.discardErr();
|
|
||||||
return new BeaconClient(command, command.getStdout(), command.getStdin()) {
|
|
||||||
|
|
||||||
// {
|
|
||||||
// new Thread(() -> {
|
|
||||||
// while (true) {
|
|
||||||
// if (!control.isRunning()) {
|
|
||||||
// close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws ConnectorException {
|
|
||||||
try {
|
|
||||||
getRawInputStream().readAllBytes();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new ConnectorException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ResponseMessage> T receiveResponse()
|
|
||||||
throws ConnectorException, ClientException, ServerException {
|
|
||||||
try {
|
|
||||||
sendEOF();
|
|
||||||
getRawOutputStream().close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new ConnectorException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.receiveResponse();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<BeaconClient> tryEstablishConnection(ClientInformation information) {
|
public static Optional<BeaconClient> tryEstablishConnection(ClientInformation information) {
|
||||||
try {
|
try {
|
||||||
return Optional.of(establishConnection(information));
|
return Optional.of(establishConnection(information));
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package io.xpipe.beacon;
|
|
||||||
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
public class XPipeInstance {
|
|
||||||
|
|
||||||
UUID uuid;
|
|
||||||
String name;
|
|
||||||
Map<ShellStore, XPipeInstance> adjacent;
|
|
||||||
List<XPipeInstance> reachable;
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package io.xpipe.beacon.exchange.cli;
|
|
||||||
|
|
||||||
import io.xpipe.beacon.RequestMessage;
|
|
||||||
import io.xpipe.beacon.ResponseMessage;
|
|
||||||
import io.xpipe.beacon.XPipeInstance;
|
|
||||||
import io.xpipe.beacon.exchange.MessageExchange;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.Value;
|
|
||||||
import lombok.extern.jackson.Jacksonized;
|
|
||||||
|
|
||||||
public class InstanceExchange implements MessageExchange {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "instance";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Jacksonized
|
|
||||||
@Builder
|
|
||||||
@Value
|
|
||||||
public static class Request implements RequestMessage {}
|
|
||||||
|
|
||||||
@Jacksonized
|
|
||||||
@Builder
|
|
||||||
@Value
|
|
||||||
public static class Response implements ResponseMessage {
|
|
||||||
@NonNull
|
|
||||||
XPipeInstance instance;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,6 @@ open module io.xpipe.beacon {
|
||||||
SinkExchange,
|
SinkExchange,
|
||||||
DrainExchange,
|
DrainExchange,
|
||||||
LaunchExchange,
|
LaunchExchange,
|
||||||
InstanceExchange,
|
|
||||||
EditStoreExchange,
|
EditStoreExchange,
|
||||||
StoreProviderListExchange,
|
StoreProviderListExchange,
|
||||||
ModeExchange,
|
ModeExchange,
|
||||||
|
|
|
@ -104,6 +104,7 @@ project.ext {
|
||||||
if (signingPassword == null) {
|
if (signingPassword == null) {
|
||||||
signingPassword = ''
|
signingPassword = ''
|
||||||
}
|
}
|
||||||
|
deeplApiKey = findProperty('DEEPL_API_KEY')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org.gradle.internal.os.OperatingSystem.current() == org.gradle.internal.os.OperatingSystem.LINUX) {
|
if (org.gradle.internal.os.OperatingSystem.current() == org.gradle.internal.os.OperatingSystem.LINUX) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.core.store;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -23,6 +24,10 @@ public final class FilePath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path toLocalPath() {
|
||||||
|
return Path.of(value);
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
import io.xpipe.core.process.ProcessControl;
|
|
||||||
|
|
||||||
public interface LaunchableStore extends DataStore {
|
public interface LaunchableStore extends DataStore {
|
||||||
|
|
||||||
default boolean canLaunch() {
|
default void launch() throws Exception {}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessControl prepareLaunchCommand() throws Exception;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ public interface ShellStore extends DataStore, LaunchableStore, FileSystemStore,
|
||||||
return new ConnectionFileSystem(control(), this);
|
return new ConnectionFileSystem(control(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
default ProcessControl prepareLaunchCommand() {
|
default ProcessControl prepareLaunchCommand() {
|
||||||
return control();
|
return control();
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue