From f67e9ad2470931bb02ebfd1368502a0f24dd2635 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 1 Jan 2024 10:04:06 +0000 Subject: [PATCH] Rework browser welcome screen --- .../app/browser/BrowserBookmarkList.java | 2 +- .../app/browser/BrowserGreetingComp.java | 2 +- .../io/xpipe/app/browser/BrowserModel.java | 45 ++++++++-------- .../xpipe/app/browser/BrowserSavedState.java | 45 +++++++++------- .../app/browser/BrowserSavedStateImpl.java | 43 +++++++++++++++ .../xpipe/app/browser/BrowserWelcomeComp.java | 53 +++++++++++-------- .../app/browser/StandaloneFileBrowser.java | 4 +- .../xpipe/app/comp/store/StoreIntroComp.java | 23 ++------ .../app/fxcomps/util/BindingsHelper.java | 12 +++-- .../java/io/xpipe/app/util/JfxHelper.java | 1 + 10 files changed, 136 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java index 5f0f8bbc..c02138ce 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java @@ -84,7 +84,7 @@ final class BrowserBookmarkList extends SimpleComp { }); }); var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(), - selectedCategory).styleClass(Styles.LEFT_PILL).grow(false, true); + selectedCategory).styleClass(Styles.LEFT_PILL); var filter = new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {}); var top = new HorizontalComp(List.of(category, filter.hgrow())).styleClass("categories").apply(struc -> { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java index 80e799c8..094c43a2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java @@ -22,7 +22,7 @@ public class BrowserGreetingComp extends SimpleComp { text = "Good afternoon"; } var r = new Label(text); - AppFont.setSize(r, 12); + AppFont.setSize(r, 7); return r; } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java index 0490801e..d785f593 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java @@ -25,17 +25,20 @@ import java.util.function.Function; @Getter public class BrowserModel { - public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER); + public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER, BrowserSavedStateImpl.load()); + private final Mode mode; private final ObservableList openFileSystems = FXCollections.observableArrayList(); private final Property selected = new SimpleObjectProperty<>(); private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); private final ObservableList selection = FXCollections.observableArrayList(); + private final BrowserSavedState savedState; @Setter private Consumer> onFinish; - public BrowserModel(Mode mode) { + public BrowserModel(Mode mode, BrowserSavedState savedState) { this.mode = mode; + this.savedState = savedState; selected.addListener((observable, oldValue, newValue) -> { if (newValue == null) { @@ -48,7 +51,7 @@ public class BrowserModel { } public void restoreState(BrowserSavedState state) { - state.getLastSystems().forEach(e -> { + state.getEntries().forEach(e -> { restoreState(e, null); }); } @@ -61,27 +64,14 @@ public class BrowserModel { } public void reset() { - var list = new ArrayList(); synchronized (BrowserModel.this) { openFileSystems.forEach(model -> { - if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) { - list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get())); - } + closeFileSystemSync(model); }); + if (savedState != null) { + savedState.save(); + } } - - // Don't override state if it is empty - if (list.size() == 0) { - return; - } - - var meaningful = list.size() > 1 || list.stream().allMatch(s -> s.getPath() != null); - if (!meaningful) { - return; - } - - var state = BrowserSavedState.builder().lastSystems(list).build(); - state.save(); } public void finishChooser() { @@ -108,13 +98,20 @@ public class BrowserModel { public void closeFileSystemAsync(OpenFileSystemModel open) { ThreadHelper.runAsync(() -> { - open.closeSync(); - synchronized (BrowserModel.this) { - openFileSystems.remove(open); - } + closeFileSystemSync(open); }); } + private void closeFileSystemSync(OpenFileSystemModel open) { + if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) && savedState != null && open.getCurrentPath().get() != null) { + savedState.add(new BrowserSavedState.Entry(open.getEntry().get().getUuid(), open.getCurrentPath().get())); + } + open.closeSync(); + synchronized (BrowserModel.this) { + openFileSystems.remove(open); + } + } + public void openFileSystemAsync(DataStoreEntryRef store, Function path, BooleanProperty externalBusy) { if (store == null) { return; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java b/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java index 7547f35f..2bac248c 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java @@ -1,27 +1,40 @@ package io.xpipe.app.browser; -import io.xpipe.app.core.AppCache; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import lombok.Builder; -import lombok.Getter; -import lombok.NonNull; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import java.util.List; import java.util.UUID; -@Value -@Jacksonized -@Builder -@Getter -public class BrowserSavedState { +public interface BrowserSavedState { - static BrowserSavedState load() { - return AppCache.get("browser-state", BrowserSavedState.class, () -> { - return null; - }); + static BrowserSavedState none() { + return new BrowserSavedState() { + @Override + public void add(Entry entry) { + + } + + @Override + public void save() { + + } + + @Override + public ObservableList getEntries() { + return FXCollections.observableArrayList(); + } + }; } + public void add(Entry entry); + + void save(); + + ObservableList getEntries(); + @Value @Jacksonized @Builder @@ -30,10 +43,4 @@ public class BrowserSavedState { UUID uuid; String path; } - - @NonNull List lastSystems; - - public void save() { - AppCache.update("browser-state", this); - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java new file mode 100644 index 00000000..6e0eddf7 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java @@ -0,0 +1,43 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.core.AppCache; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import lombok.Builder; +import lombok.Getter; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@Jacksonized +@Builder +@Getter +public class BrowserSavedStateImpl implements BrowserSavedState { + + static BrowserSavedStateImpl load() { + return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> { + return new BrowserSavedStateImpl(FXCollections.observableArrayList()); + }); + } + + ObservableList lastSystems; + + @Override + public synchronized void add(BrowserSavedState.Entry entry) { + lastSystems.removeIf(s -> s.getUuid().equals(entry.getUuid())); + lastSystems.addFirst(entry); + if (lastSystems.size() > 10) { + lastSystems.removeLast(); + } + } + + @Override + public void save() { + AppCache.update("browser-state", this); + } + + @Override + public ObservableList getEntries() { + return lastSystems; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java index cab73f66..5824b8da 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java @@ -5,19 +5,21 @@ import atlantafx.base.theme.Styles; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.TileButtonComp; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.impl.LabelComp; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.JfxHelper; import io.xpipe.app.util.ThreadHelper; +import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; -import javafx.collections.FXCollections; import javafx.geometry.Insets; import javafx.geometry.Orientation; +import javafx.geometry.Pos; import javafx.scene.control.Label; -import javafx.scene.control.Separator; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; @@ -33,33 +35,26 @@ public class BrowserWelcomeComp extends SimpleComp { @Override protected Region createSimple() { - var state = BrowserSavedState.load(); + var state = model.getSavedState(); var welcome = new BrowserGreetingComp().createSimple(); - var vbox = new VBox(welcome, new Spacer(Orientation.VERTICAL)); + var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL)); + vbox.setAlignment(Pos.CENTER_LEFT); var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75).padding(new Insets(5, 0, 0, 0)).createRegion(); var hbox = new HBox(img, vbox); + hbox.setAlignment(Pos.CENTER_LEFT); hbox.setSpacing(15); if (state == null) { var header = new Label("Here you will be able to see where you left off last time you exited XPipe."); - AppFont.header(header); vbox.getChildren().add(header); hbox.setPadding(new Insets(40, 40, 40, 50)); return new VBox(hbox); } - var header = new Label("Last time you were connected to the following systems:"); - header.getStyleClass().add(Styles.TEXT_MUTED); - AppFont.header(header); - vbox.getChildren().add(header); - - var storeList = new VBox(); - storeList.setSpacing(8); - - var list = FXCollections.observableList(state.getLastSystems().stream().filter(e -> { + var list = BindingsHelper.filteredContentBinding(state.getEntries(), e -> { var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); if (entry.isEmpty()) { return false; @@ -70,8 +65,20 @@ public class BrowserWelcomeComp extends SimpleComp { } return true; - }).toList()); - var box = new ListBoxViewComp<>(list, list, e -> { + }); + var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list); + + var header = new LabelComp(Bindings.createStringBinding(() -> { + return !empty.get() ? "Last time you were connected to the following systems:" : + "Here you will be able to see where you left off last time you exited XPipe."; + }, empty)).createRegion(); + header.getStyleClass().add(Styles.TEXT_MUTED); + vbox.getChildren().add(header); + + var storeList = new VBox(); + storeList.setSpacing(8); + + var listBox = new ListBoxViewComp<>(list, list, e -> { var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore()); var view = PrettyImageHelper.ofFixedSquare(graphic, 45); @@ -83,26 +90,26 @@ public class BrowserWelcomeComp extends SimpleComp { ThreadHelper.runAsync(() -> { model.restoreState(e, disable); }); - }).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false); + }).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-listBox").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false); }).apply(struc -> { VBox vBox = (VBox) struc.get().getContent(); vBox.setSpacing(10); - }).createRegion(); + }).hide(empty).createRegion(); var layout = new VBox(); layout.getStyleClass().add("welcome"); layout.setPadding(new Insets(40, 40, 40, 50)); layout.setSpacing(18); layout.getChildren().add(hbox); - layout.getChildren().add(new Separator(Orientation.HORIZONTAL)); - layout.getChildren().add(box); + layout.getChildren().add(Comp.separator().hide(empty).createRegion()); + layout.getChildren().add(listBox); VBox.setVgrow(layout.getChildren().get(2), Priority.NEVER); - layout.getChildren().add(new Separator(Orientation.HORIZONTAL)); + layout.getChildren().add(Comp.separator().hide(empty).createRegion()); var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> { model.restoreState(state); actionEvent.consume(); - }).grow(true, false).accessibleTextKey("restoreAllSessions"); + }).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions"); layout.getChildren().add(tile.createRegion()); return layout; diff --git a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java b/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java index 2740974a..9bf59b33 100644 --- a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java +++ b/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java @@ -41,7 +41,7 @@ public class StandaloneFileBrowser { public static void openSingleFile(Supplier> store, Consumer file) { PlatformThread.runLaterIfNeeded(() -> { - var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER); + var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null); var comp = new BrowserComp(model) .apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> AppFont.normal(struc.get())); @@ -57,7 +57,7 @@ public class StandaloneFileBrowser { public static void saveSingleFile(Property file) { PlatformThread.runLaterIfNeeded(() -> { - var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_SAVE); + var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_SAVE, null); var comp = new BrowserComp(model) .apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> AppFont.normal(struc.get())); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java index 52b1ac2b..b85d8c91 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java @@ -5,13 +5,11 @@ import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.util.Hyperlinks; import io.xpipe.app.util.ScanAlert; import javafx.beans.property.SimpleStringProperty; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.control.Button; -import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.HBox; @@ -26,7 +24,6 @@ public class StoreIntroComp extends SimpleComp { public Region createSimple() { var title = new Label(AppI18n.get("storeIntroTitle")); AppFont.setSize(title, 7); - title.getStyleClass().add("title-header"); var introDesc = new Label(AppI18n.get("storeIntroDescription")); @@ -41,22 +38,10 @@ public class StoreIntroComp extends SimpleComp { var scanPane = new StackPane(scanButton); scanPane.setAlignment(Pos.CENTER); - var dofi = new FontIcon("mdi2b-book-open-variant"); - var documentation = new Label(AppI18n.get("introDocumentation"), dofi); - documentation.heightProperty().addListener((c, o, n) -> { - dofi.iconSizeProperty().set(n.intValue()); - }); - var docLink = new Hyperlink(Hyperlinks.DOCUMENTATION); - docLink.setOnAction(e -> { - Hyperlinks.open(Hyperlinks.DOCUMENTATION); - }); - var docLinkPane = new StackPane(docLink); - docLinkPane.setAlignment(Pos.CENTER); - - var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 180).createRegion(); - var hbox = new HBox(img, new VBox( - title, introDesc, new Separator(Orientation.HORIZONTAL), machine - )); + var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion(); + var text = new VBox(title, introDesc, new Separator(Orientation.HORIZONTAL), machine); + text.setAlignment(Pos.CENTER_LEFT); + var hbox = new HBox(img, text); hbox.setSpacing(35); hbox.setAlignment(Pos.CENTER); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java index d34f5e72..87b54f34 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java @@ -289,13 +289,15 @@ public class BindingsHelper { var newSet = new HashSet<>(newList); // Only add missing element - if (targetSet.size() + 1 == newList.size() && newSet.containsAll(targetSet)) { + if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) { var l = new HashSet<>(newSet); l.removeAll(targetSet); - var found = l.iterator().next(); - var index = newList.indexOf(found); - target.add(index, found); - return; + if (l.size() > 0) { + var found = l.iterator().next(); + var index = newList.indexOf(found); + target.add(index, found); + return; + } } // Only remove not needed element diff --git a/app/src/main/java/io/xpipe/app/util/JfxHelper.java b/app/src/main/java/io/xpipe/app/util/JfxHelper.java index 3fbcc9e9..ca9654df 100644 --- a/app/src/main/java/io/xpipe/app/util/JfxHelper.java +++ b/app/src/main/java/io/xpipe/app/util/JfxHelper.java @@ -58,6 +58,7 @@ public class JfxHelper { var desc = new Label(descString); AppFont.small(desc); var text = new VBox(header, new Spacer(), desc); + text.setAlignment(Pos.CENTER_LEFT); if (image == null) { return text;