mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Rework browser intro and about pages
This commit is contained in:
parent
c2ff618698
commit
26c3c16872
27 changed files with 698 additions and 200 deletions
|
@ -108,6 +108,7 @@ List<String> jvmRunArgs = [
|
||||||
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
|
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
|
||||||
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app",
|
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app",
|
||||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app',
|
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app',
|
||||||
|
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app',
|
||||||
"-Xmx8g",
|
"-Xmx8g",
|
||||||
"-Dio.xpipe.app.arch=$rootProject.arch",
|
"-Dio.xpipe.app.arch=$rootProject.arch",
|
||||||
"--enable-preview",
|
"--enable-preview",
|
||||||
|
|
|
@ -98,6 +98,12 @@ final class BrowserBookmarkList extends SimpleComp {
|
||||||
private final Node imageView = new PrettyImageComp(img, 20, 20).createRegion();
|
private final Node imageView = new PrettyImageComp(img, 20, 20).createRegion();
|
||||||
private final BooleanProperty busy = new SimpleBooleanProperty(false);
|
private final BooleanProperty busy = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefWidth(double height) {
|
||||||
|
// This makes the cell always properly cut of any overflow of text
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
private StoreCell(TreeView<?> t) {
|
private StoreCell(TreeView<?> t) {
|
||||||
disableProperty().bind(busy);
|
disableProperty().bind(busy);
|
||||||
setAccessibleRole(AccessibleRole.BUTTON);
|
setAccessibleRole(AccessibleRole.BUTTON);
|
||||||
|
|
|
@ -6,7 +6,9 @@ import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.browser.icon.DirectoryType;
|
import io.xpipe.app.browser.icon.DirectoryType;
|
||||||
import io.xpipe.app.browser.icon.FileIconManager;
|
import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
import io.xpipe.app.browser.icon.FileType;
|
import io.xpipe.app.browser.icon.FileType;
|
||||||
|
import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
|
@ -30,6 +32,7 @@ import javafx.scene.input.DragEvent;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static atlantafx.base.theme.Styles.DENSE;
|
import static atlantafx.base.theme.Styles.DENSE;
|
||||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||||
|
@ -105,13 +108,14 @@ public class BrowserComp extends SimpleComp {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
selected.getChildren()
|
selected.getChildren()
|
||||||
.setAll(c.getList().stream()
|
.setAll(c.getList().stream()
|
||||||
.map(s -> {
|
.map(s -> {
|
||||||
var field = new TextField(s.getRawFileEntry().getPath());
|
var field =
|
||||||
field.setEditable(false);
|
new TextField(s.getRawFileEntry().getPath());
|
||||||
field.setPrefWidth(400);
|
field.setEditable(false);
|
||||||
return field;
|
field.setPrefWidth(400);
|
||||||
})
|
return field;
|
||||||
.toList());
|
})
|
||||||
|
.toList());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||||
|
@ -130,9 +134,21 @@ public class BrowserComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node createTabs() {
|
private Node createTabs() {
|
||||||
var stack = new StackPane();
|
var multi = new MultiContentComp(Map.of(
|
||||||
var tabs = createTabPane();
|
Comp.of(() -> createTabPane()),
|
||||||
stack.getChildren().add(tabs);
|
Bindings.isNotEmpty(model.getOpenFileSystems()),
|
||||||
|
new BrowserWelcomeComp(model),
|
||||||
|
Bindings.isEmpty(model.getOpenFileSystems())));
|
||||||
|
return multi.createRegion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TabPane createTabPane() {
|
||||||
|
var tabs = new TabPane();
|
||||||
|
tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
|
||||||
|
tabs.setTabMinWidth(Region.USE_COMPUTED_SIZE);
|
||||||
|
tabs.setTabClosingPolicy(ALL_TABS);
|
||||||
|
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||||
|
toggleStyleClass(tabs, DENSE);
|
||||||
|
|
||||||
var map = new HashMap<OpenFileSystemModel, Tab>();
|
var map = new HashMap<OpenFileSystemModel, Tab>();
|
||||||
|
|
||||||
|
@ -217,16 +233,6 @@ public class BrowserComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TabPane createTabPane() {
|
|
||||||
var tabs = new TabPane();
|
|
||||||
tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
|
|
||||||
tabs.setTabMinWidth(Region.USE_COMPUTED_SIZE);
|
|
||||||
tabs.setTabClosingPolicy(ALL_TABS);
|
|
||||||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
|
||||||
toggleStyleClass(tabs, DENSE);
|
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,8 +247,7 @@ public class BrowserComp extends SimpleComp {
|
||||||
.bind(Bindings.createDoubleBinding(
|
.bind(Bindings.createDoubleBinding(
|
||||||
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
||||||
|
|
||||||
var image = DataStoreProviders.byStore(model.getStore())
|
var image = DataStoreProviders.byStore(model.getStore()).getDisplayIconFileName(model.getStore());
|
||||||
.getDisplayIconFileName(model.getStore());
|
|
||||||
var logo = new PrettyImageComp(new SimpleStringProperty(image), 20, 20).createRegion();
|
var logo = new PrettyImageComp(new SimpleStringProperty(image), 20, 20).createRegion();
|
||||||
|
|
||||||
var label = new Label(model.getName());
|
var label = new Label(model.getName());
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class BrowserGreetingComp extends SimpleComp {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var ldt = LocalDateTime.now();
|
||||||
|
var hour = ldt.getHour();
|
||||||
|
String text;
|
||||||
|
if (hour > 18 || hour < 5) {
|
||||||
|
text = "Good evening";
|
||||||
|
} else if (hour < 12) {
|
||||||
|
text = "Good morning";
|
||||||
|
} else {
|
||||||
|
text = "Good afternoon";
|
||||||
|
}
|
||||||
|
var r = new Label(text);
|
||||||
|
AppFont.setSize(r, 12);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.BusyProperty;
|
import io.xpipe.app.util.BusyProperty;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.impl.FileStore;
|
import io.xpipe.core.impl.FileStore;
|
||||||
|
@ -14,9 +15,7 @@ import javafx.collections.ObservableList;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -68,6 +67,16 @@ public class BrowserModel {
|
||||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel();
|
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel();
|
||||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
var map = new LinkedHashMap<UUID, String>();
|
||||||
|
openFileSystems.forEach(model -> {
|
||||||
|
var storageEntry = DataStorage.get().getStoreEntryIfPresent(model.getStore());
|
||||||
|
storageEntry.ifPresent(entry -> map.put(entry.getUuid(), model.getCurrentPath().get()));
|
||||||
|
});
|
||||||
|
var state = new BrowserSavedState(map);
|
||||||
|
state.save();
|
||||||
|
}
|
||||||
|
|
||||||
public void finishChooser() {
|
public void finishChooser() {
|
||||||
if (!getMode().isChooser()) {
|
if (!getMode().isChooser()) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class BrowserSavedState {
|
||||||
|
|
||||||
|
static BrowserSavedState load() {
|
||||||
|
var state = AppCache.get("browser-state", BrowserSavedState.class, () -> {
|
||||||
|
return new BrowserSavedState();
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
public static class RecentEntry {
|
||||||
|
|
||||||
|
String directory;
|
||||||
|
Instant time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final LinkedHashMap<UUID, String> lastSystems;
|
||||||
|
|
||||||
|
public BrowserSavedState() {
|
||||||
|
lastSystems = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BrowserSavedState(@NonNull LinkedHashMap<UUID, String> lastSystems) {
|
||||||
|
this.lastSystems = lastSystems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
AppCache.update("browser-state", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open(FileSystemStore store) {
|
||||||
|
var storageEntry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||||
|
storageEntry.ifPresent(entry -> lastSystems.put(entry.getUuid(), null));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
public class BrowserWelcomeComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final BrowserModel model;
|
||||||
|
|
||||||
|
public BrowserWelcomeComp(BrowserModel model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var state = BrowserSavedState.load();
|
||||||
|
var showList = state.getLastSystems().size() > 1
|
||||||
|
|| (state.getLastSystems().size() == 1
|
||||||
|
&& state.getLastSystems().values().stream().allMatch(s -> s != null));
|
||||||
|
|
||||||
|
var welcome = new BrowserGreetingComp().createSimple();
|
||||||
|
|
||||||
|
var vbox = new VBox(welcome);
|
||||||
|
vbox.setPadding(new Insets(40, 40, 40, 50));
|
||||||
|
vbox.setSpacing(18);
|
||||||
|
if (!showList) {
|
||||||
|
return vbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
var header = new Label("Last time you were connected to the following systems:");
|
||||||
|
AppFont.header(header);
|
||||||
|
vbox.getChildren().add(header);
|
||||||
|
|
||||||
|
var storeList = new VBox();
|
||||||
|
storeList.setPadding(new Insets(0, 0, 0, 10));
|
||||||
|
storeList.setSpacing(8);
|
||||||
|
state.getLastSystems().forEach((uuid, s) -> {
|
||||||
|
var entry = DataStorage.get().getStoreEntry(uuid);
|
||||||
|
if (entry.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var graphic =
|
||||||
|
entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
||||||
|
var view = new PrettyImageComp(new SimpleStringProperty(graphic), 24, 24);
|
||||||
|
var l = new Label(entry.get().getName() + (s != null ? ": " + s : ""), view.createRegion());
|
||||||
|
l.setGraphicTextGap(10);
|
||||||
|
storeList.getChildren().add(l);
|
||||||
|
});
|
||||||
|
|
||||||
|
vbox.getChildren().add(storeList);
|
||||||
|
vbox.getChildren().add(new Spacer(20));
|
||||||
|
|
||||||
|
var restoreLabel = new Label("Do you want to restore these sessions?");
|
||||||
|
AppFont.header(restoreLabel);
|
||||||
|
vbox.getChildren().add(restoreLabel);
|
||||||
|
|
||||||
|
var restoreButton = new Button("Restore sessions");
|
||||||
|
vbox.getChildren().add(restoreButton);
|
||||||
|
|
||||||
|
return vbox;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.comp;
|
||||||
|
|
||||||
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.about.AboutTabComp;
|
|
||||||
import io.xpipe.app.comp.base.SideMenuBarComp;
|
import io.xpipe.app.comp.base.SideMenuBarComp;
|
||||||
import io.xpipe.app.comp.storage.store.StoreLayoutComp;
|
import io.xpipe.app.comp.storage.store.StoreLayoutComp;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
|
@ -48,11 +47,10 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
|
||||||
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
|
// new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
|
||||||
new SideMenuBarComp.Entry(
|
new SideMenuBarComp.Entry(
|
||||||
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),
|
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this))));
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
||||||
// StorageLayoutComp()),
|
// StorageLayoutComp()),
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp()),
|
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
|
||||||
new SideMenuBarComp.Entry(AppI18n.observable("about"), "mdi2p-package-variant", new AboutTabComp())));
|
|
||||||
if (AppProperties.get().isDeveloperMode()) {
|
if (AppProperties.get().isDeveloperMode()) {
|
||||||
l.add(new SideMenuBarComp.Entry(
|
l.add(new SideMenuBarComp.Entry(
|
||||||
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package io.xpipe.app.comp.about;
|
|
||||||
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
|
||||||
import io.xpipe.app.util.DynamicOptionsBuilder;
|
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
|
||||||
import javafx.scene.control.Hyperlink;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.SplitPane;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class AboutTabComp extends Comp<CompStructure<?>> {
|
|
||||||
|
|
||||||
private Region createDepsList() {
|
|
||||||
var deps = new ThirdPartyDependencyListComp().createRegion();
|
|
||||||
return deps;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comp<?> hyperlink(String link) {
|
|
||||||
return Comp.of(() -> {
|
|
||||||
var hl = new Hyperlink(link);
|
|
||||||
hl.setOnAction(e -> {
|
|
||||||
Hyperlinks.open(link);
|
|
||||||
});
|
|
||||||
hl.setMaxWidth(450);
|
|
||||||
return hl;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comp<?> createLinks() {
|
|
||||||
return new DynamicOptionsBuilder(false)
|
|
||||||
.addTitle("links")
|
|
||||||
//.addComp(AppI18n.observable("documentation"), hyperlink(Hyperlinks.DOCUMENTATION), null)
|
|
||||||
.addComp(AppI18n.observable("securityPolicy"), hyperlink(Hyperlinks.SECURITY), null)
|
|
||||||
.addComp(AppI18n.observable("privacy"), hyperlink(Hyperlinks.PRIVACY), null)
|
|
||||||
.addComp(AppI18n.observable("discord"), hyperlink(Hyperlinks.DISCORD), null)
|
|
||||||
.addComp(AppI18n.observable("slack"), hyperlink(Hyperlinks.SLACK), null)
|
|
||||||
.addComp(AppI18n.observable("github"), hyperlink(Hyperlinks.GITHUB), null)
|
|
||||||
.buildComp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Region createThirdPartyDeps() {
|
|
||||||
var label = new Label(AppI18n.get("openSourceNotices"), new FontIcon("mdi2o-open-source-initiative"));
|
|
||||||
label.getStyleClass().add("open-source-header");
|
|
||||||
var list = createDepsList();
|
|
||||||
var box = new VBox(label, list);
|
|
||||||
box.getStyleClass().add("open-source-notices");
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompStructure<?> createBase() {
|
|
||||||
var props = new PropertiesComp();
|
|
||||||
var update = new UpdateCheckComp();
|
|
||||||
var box = new VerticalComp(List.of(props, update, createLinks(), new BrowseDirectoryComp()))
|
|
||||||
.apply(s -> s.get().setFillWidth(true))
|
|
||||||
.styleClass("information");
|
|
||||||
|
|
||||||
return Comp.derive(box, boxS -> {
|
|
||||||
var bp = new SplitPane();
|
|
||||||
bp.getItems().add(boxS);
|
|
||||||
var deps = createThirdPartyDeps();
|
|
||||||
bp.getItems().add(createThirdPartyDeps());
|
|
||||||
deps.prefWidthProperty().bind(bp.widthProperty().divide(2));
|
|
||||||
boxS.prefWidthProperty().bind(bp.widthProperty().divide(2));
|
|
||||||
bp.getStyleClass().add("about-tab");
|
|
||||||
return bp;
|
|
||||||
})
|
|
||||||
.createStructure();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package io.xpipe.app.comp.about;
|
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.core.AppLogs;
|
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.issue.UserReportComp;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
|
||||||
import io.xpipe.app.util.*;
|
|
||||||
import io.xpipe.core.impl.FileNames;
|
|
||||||
import io.xpipe.core.process.OsType;
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.util.XPipeInstallation;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
|
|
||||||
public class BrowseDirectoryComp extends SimpleComp {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Region createSimple() {
|
|
||||||
var b = new DynamicOptionsBuilder(false)
|
|
||||||
.addComp(
|
|
||||||
"issueReporter",
|
|
||||||
new ButtonComp(AppI18n.observable("reportIssue"), () -> {
|
|
||||||
var event = ErrorEvent.fromMessage("User Report");
|
|
||||||
if (AppLogs.get().isWriteToFile()) {
|
|
||||||
event.attachment(AppLogs.get().getSessionLogsDirectory());
|
|
||||||
}
|
|
||||||
UserReportComp.show(event.build());
|
|
||||||
}),
|
|
||||||
null)
|
|
||||||
.addComp(
|
|
||||||
"logFile",
|
|
||||||
new ButtonComp(AppI18n.observable("openCurrentLogFile"), () -> {
|
|
||||||
FileOpener.openInTextEditor(AppLogs.get()
|
|
||||||
.getSessionLogsDirectory()
|
|
||||||
.resolve("xpipe.log")
|
|
||||||
.toString());
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
if (AppPrefs.get().developerMode().getValue()) {
|
|
||||||
b.addComp(
|
|
||||||
"launchDebugMode",
|
|
||||||
new ButtonComp(AppI18n.observable("launchDebugMode"), () -> {
|
|
||||||
OperationMode.executeAfterShutdown(() -> {
|
|
||||||
try (var sc = ShellStore.createLocal().control().start()) {
|
|
||||||
var script = FileNames.join(
|
|
||||||
XPipeInstallation.getCurrentInstallationBasePath()
|
|
||||||
.toString(),
|
|
||||||
XPipeInstallation.getDaemonDebugScriptPath(sc.getOsType()));
|
|
||||||
if (sc.getOsType().equals(OsType.WINDOWS)) {
|
|
||||||
sc.executeSimpleCommand(ScriptHelper.createDetachCommand(sc, "\"" + script + "\""));
|
|
||||||
} else {
|
|
||||||
TerminalHelper.open("XPipe Debug", "\"" + script + "\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
DesktopHelper.browsePath(AppLogs.get().getSessionLogsDirectory());
|
|
||||||
}),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
return b.addComp(
|
|
||||||
"installationFiles",
|
|
||||||
new ButtonComp(AppI18n.observable("openInstallationDirectory"), () -> {
|
|
||||||
DesktopHelper.browsePath(XPipeInstallation.getCurrentInstallationBasePath());
|
|
||||||
}),
|
|
||||||
null)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class DescriptionButtonComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final ObservableValue<String> name;
|
||||||
|
private final ObservableValue<String> description;
|
||||||
|
private final ObservableValue<String> icon;
|
||||||
|
private final Consumer<ActionEvent> action;
|
||||||
|
|
||||||
|
public DescriptionButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
|
||||||
|
this.name = AppI18n.observable(nameKey);
|
||||||
|
this.description = AppI18n.observable(descriptionKey);
|
||||||
|
this.icon = new SimpleStringProperty(icon);
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var bt = new Button();
|
||||||
|
bt.setGraphic(createNamedEntry());
|
||||||
|
Styles.toggleStyleClass(bt, Styles.FLAT);
|
||||||
|
bt.setOnAction(e -> {
|
||||||
|
action.accept(e);
|
||||||
|
});
|
||||||
|
return bt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Region createNamedEntry() {
|
||||||
|
var header = new Label();
|
||||||
|
header.textProperty().bind(PlatformThread.sync(name));
|
||||||
|
var desc = new Label();
|
||||||
|
desc.textProperty().bind(PlatformThread.sync(description));
|
||||||
|
AppFont.small(desc);
|
||||||
|
desc.setOpacity(0.65);
|
||||||
|
var text = new VBox(header, desc);
|
||||||
|
text.setSpacing(2);
|
||||||
|
|
||||||
|
var fi = new FontIcon();
|
||||||
|
SimpleChangeListener.apply(PlatformThread.sync(icon), val -> {
|
||||||
|
fi.setIconLiteral(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
var pane = new StackPane(fi);
|
||||||
|
var hbox = new HBox(pane, text);
|
||||||
|
hbox.setSpacing(8);
|
||||||
|
pane.prefWidthProperty()
|
||||||
|
.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> (header.getHeight() + desc.getHeight()) * 0.6,
|
||||||
|
header.heightProperty(),
|
||||||
|
desc.heightProperty()));
|
||||||
|
pane.prefHeightProperty()
|
||||||
|
.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> header.getHeight() + desc.getHeight() + 2,
|
||||||
|
header.heightProperty(),
|
||||||
|
desc.heightProperty()));
|
||||||
|
pane.prefHeightProperty().addListener((c, o, n) -> {
|
||||||
|
var size = Math.min(n.intValue(), 100);
|
||||||
|
fi.setIconSize((int) (size * 0.55));
|
||||||
|
});
|
||||||
|
return hbox;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.core.mode;
|
package io.xpipe.app.core.mode;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.BrowserModel;
|
||||||
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
||||||
import io.xpipe.app.comp.storage.store.StoreViewState;
|
import io.xpipe.app.comp.storage.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
|
@ -52,6 +53,7 @@ public class BaseMode extends OperationMode {
|
||||||
@Override
|
@Override
|
||||||
public void finalTeardown() {
|
public void finalTeardown() {
|
||||||
TrackEvent.info("mode", "Background mode shutdown started");
|
TrackEvent.info("mode", "Background mode shutdown started");
|
||||||
|
BrowserModel.DEFAULT.reset();
|
||||||
AppSocketServer.reset();
|
AppSocketServer.reset();
|
||||||
SourceCollectionViewState.reset();
|
SourceCollectionViewState.reset();
|
||||||
StoreViewState.reset();
|
StoreViewState.reset();
|
||||||
|
|
177
app/src/main/java/io/xpipe/app/prefs/AboutComp.java
Normal file
177
app/src/main/java/io/xpipe/app/prefs/AboutComp.java
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.base.DescriptionButtonComp;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.AppLogs;
|
||||||
|
import io.xpipe.app.core.AppWindowHelper;
|
||||||
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.issue.UserReportComp;
|
||||||
|
import io.xpipe.app.util.*;
|
||||||
|
import io.xpipe.core.impl.FileNames;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AboutComp extends Comp<CompStructure<?>> {
|
||||||
|
|
||||||
|
private Region createDepsList() {
|
||||||
|
var deps = new ThirdPartyDependencyListComp().createRegion();
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> createActions() {
|
||||||
|
return new OptionsBuilder()
|
||||||
|
.addTitle("usefulActions")
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("reportIssue", "reportIssueDescription", "mdal-bug_report", e -> {
|
||||||
|
var event = ErrorEvent.fromMessage("User Report");
|
||||||
|
if (AppLogs.get().isWriteToFile()) {
|
||||||
|
event.attachment(AppLogs.get().getSessionLogsDirectory());
|
||||||
|
}
|
||||||
|
UserReportComp.show(event.build());
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp(
|
||||||
|
"openCurrentLogFile",
|
||||||
|
"openCurrentLogFileDescription",
|
||||||
|
"mdmz-text_snippet",
|
||||||
|
e -> {
|
||||||
|
FileOpener.openInTextEditor(AppLogs.get()
|
||||||
|
.getSessionLogsDirectory()
|
||||||
|
.resolve("xpipe.log")
|
||||||
|
.toString());
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp(
|
||||||
|
"launchDebugMode", "launchDebugModeDescription", "mdmz-refresh", e -> {
|
||||||
|
OperationMode.executeAfterShutdown(() -> {
|
||||||
|
try (var sc = ShellStore.createLocal()
|
||||||
|
.control()
|
||||||
|
.start()) {
|
||||||
|
var script = FileNames.join(
|
||||||
|
XPipeInstallation.getCurrentInstallationBasePath()
|
||||||
|
.toString(),
|
||||||
|
XPipeInstallation.getDaemonDebugScriptPath(sc.getOsType()));
|
||||||
|
if (sc.getOsType().equals(OsType.WINDOWS)) {
|
||||||
|
sc.executeSimpleCommand(ScriptHelper.createDetachCommand(
|
||||||
|
sc, "\"" + script + "\""));
|
||||||
|
} else {
|
||||||
|
TerminalHelper.open("XPipe Debug", "\"" + script + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DesktopHelper.browsePath(
|
||||||
|
AppLogs.get().getSessionLogsDirectory());
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp(
|
||||||
|
"openInstallationDirectory",
|
||||||
|
"openInstallationDirectoryDescription",
|
||||||
|
"mdomz-snippet_folder",
|
||||||
|
e -> {
|
||||||
|
DesktopHelper.browsePath(
|
||||||
|
XPipeInstallation.getCurrentInstallationBasePath());
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.buildComp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> createLinks() {
|
||||||
|
return new OptionsBuilder()
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp(
|
||||||
|
"securityPolicy", "securityPolicyDescription", "mdrmz-security", e -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.SECURITY);
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("privacy", "privacyDescription", "mdomz-privacy_tip", e -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.PRIVACY);
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("thirdParty", "thirdPartyDescription", "mdi2o-open-source-initiative", e -> {
|
||||||
|
AppWindowHelper.sideWindow(AppI18n.get("openSourceNotices"), stage -> Comp.of(() -> createThirdPartyDeps()), true, null).show();
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("discord", "discordDescription", "mdi2d-discord", e -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.DISCORD);
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("slack", "slackDescription", "mdi2s-slack", e -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.SLACK);
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.addComp(
|
||||||
|
new DescriptionButtonComp("github", "githubDescription", "mdi2g-github", e -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.GITHUB);
|
||||||
|
e.consume();
|
||||||
|
})
|
||||||
|
.grow(true, false),
|
||||||
|
null)
|
||||||
|
.buildComp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createThirdPartyDeps() {
|
||||||
|
var list = new ThirdPartyDependencyListComp().createRegion();
|
||||||
|
list.getStyleClass().add("open-source-notices");
|
||||||
|
var sp = new ScrollPane(list);
|
||||||
|
sp.setFitToWidth(true);
|
||||||
|
sp.setPrefWidth(600);
|
||||||
|
sp.setPrefHeight(500);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompStructure<?> createBase() {
|
||||||
|
var props = new PropertiesComp().padding(new Insets(0, 0, 0, 15));
|
||||||
|
var update = Comp.derive(new UpdateCheckComp(), u -> {
|
||||||
|
var sp = new StackPane(u);
|
||||||
|
sp.setAlignment(Pos.CENTER);
|
||||||
|
sp.setPadding(new Insets(10, 0, 10, 15));
|
||||||
|
return sp;
|
||||||
|
})
|
||||||
|
.grow(true, false);
|
||||||
|
var box = new VerticalComp(List.of(props, update, createLinks(), createActions()))
|
||||||
|
.apply(s -> s.get().setFillWidth(true))
|
||||||
|
.apply(struc -> struc.get().setSpacing(15))
|
||||||
|
.styleClass("information")
|
||||||
|
.styleClass("about-tab")
|
||||||
|
.apply(struc -> struc.get().setPrefWidth(800));
|
||||||
|
return box.createStructure();
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -490,8 +491,15 @@ public class AppPrefs {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
private AppPreferencesFx createPreferences() {
|
private AppPreferencesFx createPreferences() {
|
||||||
|
var ctr = Setting.class.getDeclaredConstructor(String.class, Element.class, Property.class);
|
||||||
|
ctr.setAccessible(true);
|
||||||
|
var s = ctr.newInstance(null, new LazyNodeElement<>(() -> new AboutComp().createRegion()), null);
|
||||||
|
|
||||||
var categories = new ArrayList<>(List.of(
|
var categories = new ArrayList<>(List.of(
|
||||||
|
Category.of(
|
||||||
|
"application", Group.of(s)),
|
||||||
Category.of(
|
Category.of(
|
||||||
"system",
|
"system",
|
||||||
Group.of(
|
Group.of(
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.prefs;
|
||||||
import com.dlsc.formsfx.model.structure.Element;
|
import com.dlsc.formsfx.model.structure.Element;
|
||||||
import com.dlsc.formsfx.model.structure.Field;
|
import com.dlsc.formsfx.model.structure.Field;
|
||||||
import com.dlsc.formsfx.model.structure.Form;
|
import com.dlsc.formsfx.model.structure.Form;
|
||||||
import com.dlsc.formsfx.model.structure.NodeElement;
|
|
||||||
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
|
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
|
||||||
import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxFormRenderer;
|
import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxFormRenderer;
|
||||||
import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxGroup;
|
import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxGroup;
|
||||||
|
@ -35,6 +34,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
@Override
|
@Override
|
||||||
public void initializeParts() {
|
public void initializeParts() {
|
||||||
super.initializeParts();
|
super.initializeParts();
|
||||||
|
grid.getStyleClass().add("grid");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,7 +73,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
|
|
||||||
SimpleControl c = (SimpleControl) ((Field) element).getRenderer();
|
SimpleControl c = (SimpleControl) ((Field) element).getRenderer();
|
||||||
c.setField((Field) element);
|
c.setField((Field) element);
|
||||||
AppFont.header(c.getFieldLabel());
|
AppFont.normal(c.getFieldLabel());
|
||||||
c.getFieldLabel().setPrefHeight(AppFont.getPixelSize(1));
|
c.getFieldLabel().setPrefHeight(AppFont.getPixelSize(1));
|
||||||
c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1));
|
c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1));
|
||||||
grid.add(c.getFieldLabel(), 0, i + rowAmount, 2, 1);
|
grid.add(c.getFieldLabel(), 0, i + rowAmount, 2, 1);
|
||||||
|
@ -81,7 +81,9 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
var canFocus = BindingsHelper.persist(c.getNode().disabledProperty().not());
|
var canFocus = BindingsHelper.persist(c.getNode().disabledProperty().not());
|
||||||
|
|
||||||
var descriptionLabel = new Label();
|
var descriptionLabel = new Label();
|
||||||
|
AppFont.medium(descriptionLabel);
|
||||||
descriptionLabel.setWrapText(true);
|
descriptionLabel.setWrapText(true);
|
||||||
|
descriptionLabel.setMaxWidth(700);
|
||||||
descriptionLabel
|
descriptionLabel
|
||||||
.disableProperty()
|
.disableProperty()
|
||||||
.bind(c.getFieldLabel().disabledProperty());
|
.bind(c.getFieldLabel().disabledProperty());
|
||||||
|
@ -89,14 +91,13 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
.opacityProperty()
|
.opacityProperty()
|
||||||
.bind(c.getFieldLabel()
|
.bind(c.getFieldLabel()
|
||||||
.opacityProperty()
|
.opacityProperty()
|
||||||
.multiply(0.8));
|
.multiply(0.65));
|
||||||
descriptionLabel
|
descriptionLabel
|
||||||
.managedProperty()
|
.managedProperty()
|
||||||
.bind(c.getFieldLabel().managedProperty());
|
.bind(c.getFieldLabel().managedProperty());
|
||||||
descriptionLabel
|
descriptionLabel
|
||||||
.visibleProperty()
|
.visibleProperty()
|
||||||
.bind(c.getFieldLabel().visibleProperty());
|
.bind(c.getFieldLabel().visibleProperty());
|
||||||
descriptionLabel.setMaxHeight(USE_PREF_SIZE);
|
|
||||||
if (AppI18n.getInstance().containsKey(descriptionKey)) {
|
if (AppI18n.getInstance().containsKey(descriptionKey)) {
|
||||||
rowAmount++;
|
rowAmount++;
|
||||||
descriptionLabel.textProperty().bind(AppI18n.observable(descriptionKey));
|
descriptionLabel.textProperty().bind(AppI18n.observable(descriptionKey));
|
||||||
|
@ -107,6 +108,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
rowAmount++;
|
rowAmount++;
|
||||||
|
|
||||||
var node = c.getNode();
|
var node = c.getNode();
|
||||||
|
AppFont.medium(c.getNode());
|
||||||
c.getFieldLabel().focusTraversableProperty().bind(canFocus);
|
c.getFieldLabel().focusTraversableProperty().bind(canFocus);
|
||||||
grid.add(node, 0, i + rowAmount, 1, 1);
|
grid.add(node, 0, i + rowAmount, 1, 1);
|
||||||
|
|
||||||
|
@ -130,8 +132,9 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
node.getStyleClass().add(styleClass.toString() + "-node");
|
node.getStyleClass().add(styleClass.toString() + "-node");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element instanceof NodeElement nodeElement) {
|
if (element instanceof LazyNodeElement<?> nodeElement) {
|
||||||
grid.add(nodeElement.getNode(), 0, i + rowAmount, GridPane.REMAINING, 1);
|
var node = nodeElement.getNode();
|
||||||
|
grid.add(node, 0, i + rowAmount, 2, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
app/src/main/java/io/xpipe/app/prefs/LazyNodeElement.java
Normal file
26
app/src/main/java/io/xpipe/app/prefs/LazyNodeElement.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
|
import com.dlsc.formsfx.model.structure.Element;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class LazyNodeElement<N extends Node> extends Element<LazyNodeElement<N>> {
|
||||||
|
|
||||||
|
protected Supplier<N> node;
|
||||||
|
|
||||||
|
public static <T extends Node> LazyNodeElement<T> of(Supplier<T> node) {
|
||||||
|
return new LazyNodeElement<>(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LazyNodeElement(Supplier<N> node) {
|
||||||
|
if (node == null) {
|
||||||
|
throw new NullPointerException("Node argument must not be null");
|
||||||
|
}
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public N getNode() {
|
||||||
|
return node.get();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.about;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.App;
|
import io.xpipe.app.core.App;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
@ -19,6 +19,7 @@ public class PropertiesComp extends SimpleComp {
|
||||||
var title = Comp.of(() -> {
|
var title = Comp.of(() -> {
|
||||||
var image = new ImageView(App.getApp().getIcon());
|
var image = new ImageView(App.getApp().getIcon());
|
||||||
image.setPreserveRatio(true);
|
image.setPreserveRatio(true);
|
||||||
|
image.setSmooth(true);
|
||||||
image.setFitHeight(40);
|
image.setFitHeight(40);
|
||||||
var label = new Label(AppI18n.get("xPipeClient"), image);
|
var label = new Label(AppI18n.get("xPipeClient"), image);
|
||||||
label.getStyleClass().add("header");
|
label.getStyleClass().add("header");
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.about;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppExtensionManager;
|
import io.xpipe.app.core.AppExtensionManager;
|
||||||
import io.xpipe.app.core.AppResources;
|
import io.xpipe.app.core.AppResources;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.about;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.about;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
|
@ -31,10 +31,9 @@ public class JfxHelper {
|
||||||
|
|
||||||
public static Region createNamedEntry(String nameString, String descString, FontIcon graphic) {
|
public static Region createNamedEntry(String nameString, String descString, FontIcon graphic) {
|
||||||
var header = new Label(nameString);
|
var header = new Label(nameString);
|
||||||
AppFont.header(header);
|
|
||||||
var desc = new Label(descString);
|
var desc = new Label(descString);
|
||||||
desc.setWrapText(true);
|
|
||||||
AppFont.small(desc);
|
AppFont.small(desc);
|
||||||
|
desc.setOpacity(0.65);
|
||||||
var text = new VBox(header, desc);
|
var text = new VBox(header, desc);
|
||||||
text.setSpacing(2);
|
text.setSpacing(2);
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ public class JfxHelper {
|
||||||
hbox.setSpacing(8);
|
hbox.setSpacing(8);
|
||||||
pane.prefWidthProperty()
|
pane.prefWidthProperty()
|
||||||
.bind(Bindings.createDoubleBinding(
|
.bind(Bindings.createDoubleBinding(
|
||||||
() -> header.getHeight() + desc.getHeight() + 2,
|
() -> (header.getHeight() + desc.getHeight()) * 0.6,
|
||||||
header.heightProperty(),
|
header.heightProperty(),
|
||||||
desc.heightProperty()));
|
desc.heightProperty()));
|
||||||
pane.prefHeightProperty()
|
pane.prefHeightProperty()
|
||||||
|
@ -53,9 +52,8 @@ public class JfxHelper {
|
||||||
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);
|
||||||
graphic.setIconSize(size);
|
graphic.setIconSize((int) (size * 0.55));
|
||||||
});
|
});
|
||||||
|
|
||||||
return hbox;
|
return hbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
108
app/src/main/java/io/xpipe/app/util/SmoothScroll.java
Normal file
108
app/src/main/java/io/xpipe/app/util/SmoothScroll.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.ScrollBar;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.input.ScrollEvent;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class SmoothScroll {
|
||||||
|
|
||||||
|
private static ScrollBar getScrollbarComponent(Node no, Orientation orientation) {
|
||||||
|
Node n = no.lookup(".scroll-bar");
|
||||||
|
if (n instanceof ScrollBar) {
|
||||||
|
final ScrollBar bar = (ScrollBar) n;
|
||||||
|
if (bar.getOrientation().equals(orientation)) {
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void smoothScrollingListView(Node n, double speed) {
|
||||||
|
smoothScrollingListView(n, speed, Orientation.VERTICAL, bounds -> bounds.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void smoothHScrollingListView(ListView<?> listView, double speed) {
|
||||||
|
smoothScrollingListView(listView, speed, Orientation.HORIZONTAL, bounds -> bounds.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void smoothScrollingListView(
|
||||||
|
Node n, double speed, Orientation orientation, Function<Bounds, Double> sizeFunc) {
|
||||||
|
((TableView) n).skinProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
ScrollBar scrollBar = getScrollbarComponent(n, orientation);
|
||||||
|
if (scrollBar == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scrollBar.setUnitIncrement(1);
|
||||||
|
final double[] frictions = {
|
||||||
|
0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003,
|
||||||
|
0.00001
|
||||||
|
};
|
||||||
|
final double[] pushes = {speed};
|
||||||
|
final double[] derivatives = new double[frictions.length];
|
||||||
|
final double[] lastVPos = {0};
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
final EventHandler<MouseEvent> dragHandler = event -> timeline.stop();
|
||||||
|
final EventHandler<ScrollEvent> scrollHandler = event -> {
|
||||||
|
scrollBar.valueProperty().set(lastVPos[0]);
|
||||||
|
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||||
|
double direction = event.getDeltaY() > 0 ? -1 : 1;
|
||||||
|
for (int i = 0; i < pushes.length; i++) {
|
||||||
|
derivatives[i] += direction * pushes[i];
|
||||||
|
}
|
||||||
|
if (timeline.getStatus() == Animation.Status.STOPPED) {
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
};
|
||||||
|
if (scrollBar.getParent() != null) {
|
||||||
|
scrollBar.getParent().addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
|
||||||
|
scrollBar.getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||||
|
}
|
||||||
|
scrollBar.parentProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
if (oldVal != null) {
|
||||||
|
oldVal.removeEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
|
||||||
|
oldVal.removeEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||||
|
}
|
||||||
|
if (newVal != null) {
|
||||||
|
newVal.addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
|
||||||
|
newVal.addEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
|
||||||
|
for (int i = 0; i < derivatives.length; i++) {
|
||||||
|
derivatives[i] *= frictions[i];
|
||||||
|
}
|
||||||
|
for (int i = 1; i < derivatives.length; i++) {
|
||||||
|
derivatives[i] += derivatives[i - 1];
|
||||||
|
}
|
||||||
|
double dy = derivatives[derivatives.length - 1];
|
||||||
|
double size = sizeFunc.apply(scrollBar.getLayoutBounds());
|
||||||
|
scrollBar.valueProperty().set(Math.min(Math.max(scrollBar.getValue() + dy / size, 0), 1));
|
||||||
|
lastVPos[0] = scrollBar.getValue();
|
||||||
|
if (Math.abs(dy) < 1) {
|
||||||
|
if (Math.abs(dy) < 0.001) {
|
||||||
|
timeline.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
timeline.setCycleCount(Animation.INDEFINITE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ noMatchingStoreFound=No suitable saved store was found
|
||||||
addStore=Add Store
|
addStore=Add Store
|
||||||
anyStreamDescription=Or choose specific type
|
anyStreamDescription=Or choose specific type
|
||||||
reportIssue=Report Issue
|
reportIssue=Report Issue
|
||||||
|
reportIssueDescription=Open the integrated issue reporter
|
||||||
|
usefulActions=Useful actions
|
||||||
stored=Saved
|
stored=Saved
|
||||||
other=Other
|
other=Other
|
||||||
remote=Remote File
|
remote=Remote File
|
||||||
|
|
|
@ -7,6 +7,8 @@ editorProgramDescription=The default text editor to use when editing any kind of
|
||||||
useSystemFont=Use system font
|
useSystemFont=Use system font
|
||||||
updates=Updates
|
updates=Updates
|
||||||
advanced=Advanced
|
advanced=Advanced
|
||||||
|
thirdParty=Open source notices
|
||||||
|
thirdPartyDescription=View the open source licenses of third-party libraries
|
||||||
workspaceLock=Workspace lock
|
workspaceLock=Workspace lock
|
||||||
workspaceLockDescription=Sets a custom password to encrypt your stored information in XPipe. This results in increased security as it provides an additional layer of encryption for your stored sensitive information. You will then be prompted to enter the password when XPipe starts.
|
workspaceLockDescription=Sets a custom password to encrypt your stored information in XPipe. This results in increased security as it provides an additional layer of encryption for your stored sensitive information. You will then be prompted to enter the password when XPipe starts.
|
||||||
useSystemFontDescription=Controls whether to use your system font or the default font used by XPipe (Roboto).
|
useSystemFontDescription=Controls whether to use your system font or the default font used by XPipe (Roboto).
|
||||||
|
@ -18,6 +20,7 @@ saveWindowLocation=Save window location
|
||||||
saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts.
|
saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts.
|
||||||
startupShutdown=Startup / Shutdown
|
startupShutdown=Startup / Shutdown
|
||||||
system=System
|
system=System
|
||||||
|
application=Application
|
||||||
updateToPrereleases=Include prereleases
|
updateToPrereleases=Include prereleases
|
||||||
updateToPrereleasesDescription=When enabled, the update check will also look for available prereleases in addition to full releases.
|
updateToPrereleasesDescription=When enabled, the update check will also look for available prereleases in addition to full releases.
|
||||||
storage=Storage
|
storage=Storage
|
||||||
|
|
|
@ -167,12 +167,17 @@ links=Useful links
|
||||||
website=Website
|
website=Website
|
||||||
documentation=Documentation
|
documentation=Documentation
|
||||||
discord=Discord
|
discord=Discord
|
||||||
|
discordDescription=Join the Discord server
|
||||||
security=Security
|
security=Security
|
||||||
securityPolicy=Security Policy
|
securityPolicy=Security information
|
||||||
|
securityPolicyDescription=Read the detailed security policy
|
||||||
privacy=Privacy Policy
|
privacy=Privacy Policy
|
||||||
|
privacyDescription=Read the privacy policy for the XPipe application
|
||||||
slack=Slack
|
slack=Slack
|
||||||
|
slackDescription=Join the Slack workspace
|
||||||
support=Support
|
support=Support
|
||||||
github=GitHub
|
github=GitHub
|
||||||
|
githubDescription=Check out the GitHub repository
|
||||||
openSourceNotices=Open Source Notices
|
openSourceNotices=Open Source Notices
|
||||||
xPipeClient=XPipe Desktop
|
xPipeClient=XPipe Desktop
|
||||||
checkForUpdates=Check for updates
|
checkForUpdates=Check for updates
|
||||||
|
@ -202,11 +207,14 @@ logFile=Log File
|
||||||
logFiles=Log Files
|
logFiles=Log Files
|
||||||
logFilesAttachment=Log Files
|
logFilesAttachment=Log Files
|
||||||
issueReporter=Issue Reporter
|
issueReporter=Issue Reporter
|
||||||
openCurrentLogFile=Open current log file
|
openCurrentLogFile=Log files
|
||||||
|
openCurrentLogFileDescription=Open the log file of the current session
|
||||||
openLogsDirectory=Open logs directory
|
openLogsDirectory=Open logs directory
|
||||||
installationFiles=Installation Files
|
installationFiles=Installation Files
|
||||||
openInstallationDirectory=Open installation directory
|
openInstallationDirectory=Installation files
|
||||||
launchDebugMode=Launch debug mode
|
openInstallationDirectoryDescription=Open XPipe installation directory
|
||||||
|
launchDebugMode=Debug mode
|
||||||
|
launchDebugModeDescription=Restart XPipe in debug mode
|
||||||
extensionInstallTitle=Download
|
extensionInstallTitle=Download
|
||||||
extensionInstallDescription=This action requires additional third party libraries that are not distributed by XPipe. You can automatically install them here. The components are then downloaded from the vendor website:
|
extensionInstallDescription=This action requires additional third party libraries that are not distributed by XPipe. You can automatically install them here. The components are then downloaded from the vendor website:
|
||||||
extensionInstallLicenseNote=By performing the download and automatic installation you agree to the terms of the third party licenses:
|
extensionInstallLicenseNote=By performing the download and automatic installation you agree to the terms of the third party licenses:
|
||||||
|
|
|
@ -37,20 +37,30 @@ visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs .tree-view {
|
.prefs .tree-view {
|
||||||
|
-fx-border-width: 0 1px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefs .grid {
|
||||||
|
-fx-padding: 1em 2.3em 0 2.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs {
|
.prefs {
|
||||||
-fx-border-width: 0;
|
-fx-border-width: 0;
|
||||||
-fx-padding: 1em 1em 4em 1em;
|
-fx-padding: 0;
|
||||||
-fx-border-color: transparent;
|
-fx-border-color: transparent;
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs > * > * {
|
.prefs .custom-text-field {
|
||||||
-fx-background-insets: 0;
|
-fx-border-width: 5px;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-border-color: transparent;
|
||||||
|
-fx-border-radius: 0;
|
||||||
|
-fx-background-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs .split-pane-divider {
|
.prefs .split-pane-divider {
|
||||||
-fx-padding: 0 ;
|
-fx-padding: 0 ;
|
||||||
dividerPositions: 0.5;
|
dividerPositions: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
core/src/main/java/io/xpipe/core/process/CommandBuilder.java
Normal file
40
core/src/main/java/io/xpipe/core/process/CommandBuilder.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package io.xpipe.core.process;
|
||||||
|
|
||||||
|
public class CommandBuilder {
|
||||||
|
|
||||||
|
public static CommandBuilder of() {
|
||||||
|
return new CommandBuilder(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CommandBuilder ofNoQuotes() {
|
||||||
|
return new CommandBuilder(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandBuilder(boolean noQuoting) {
|
||||||
|
this.noQuoting = noQuoting;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean noQuoting;
|
||||||
|
private final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
public CommandBuilder add(String s) {
|
||||||
|
if (!builder.isEmpty()) {
|
||||||
|
builder.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.contains(" ") || s.contains("\t")) {
|
||||||
|
if (noQuoting) {
|
||||||
|
throw new IllegalArgumentException("No quoting rule conflicts with spaces an argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
s = "\"" + s + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String build() {
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue