Rework browser welcome screen

This commit is contained in:
crschnick 2024-01-01 10:04:06 +00:00
parent 2bc508d448
commit f67e9ad247
10 changed files with 136 additions and 94 deletions

View file

@ -84,7 +84,7 @@ final class BrowserBookmarkList extends SimpleComp {
}); });
}); });
var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(), 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 filter = new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
var top = new HorizontalComp(List.of(category, filter.hgrow())).styleClass("categories").apply(struc -> { var top = new HorizontalComp(List.of(category, filter.hgrow())).styleClass("categories").apply(struc -> {

View file

@ -22,7 +22,7 @@ public class BrowserGreetingComp extends SimpleComp {
text = "Good afternoon"; text = "Good afternoon";
} }
var r = new Label(text); var r = new Label(text);
AppFont.setSize(r, 12); AppFont.setSize(r, 7);
return r; return r;
} }
} }

View file

@ -25,17 +25,20 @@ import java.util.function.Function;
@Getter @Getter
public class BrowserModel { 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 Mode mode;
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList(); private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>(); private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList(); private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final BrowserSavedState savedState;
@Setter @Setter
private Consumer<List<FileReference>> onFinish; private Consumer<List<FileReference>> onFinish;
public BrowserModel(Mode mode) { public BrowserModel(Mode mode, BrowserSavedState savedState) {
this.mode = mode; this.mode = mode;
this.savedState = savedState;
selected.addListener((observable, oldValue, newValue) -> { selected.addListener((observable, oldValue, newValue) -> {
if (newValue == null) { if (newValue == null) {
@ -48,7 +51,7 @@ public class BrowserModel {
} }
public void restoreState(BrowserSavedState state) { public void restoreState(BrowserSavedState state) {
state.getLastSystems().forEach(e -> { state.getEntries().forEach(e -> {
restoreState(e, null); restoreState(e, null);
}); });
} }
@ -61,27 +64,14 @@ public class BrowserModel {
} }
public void reset() { public void reset() {
var list = new ArrayList<BrowserSavedState.Entry>();
synchronized (BrowserModel.this) { synchronized (BrowserModel.this) {
openFileSystems.forEach(model -> { openFileSystems.forEach(model -> {
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) { closeFileSystemSync(model);
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
}
}); });
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() { public void finishChooser() {
@ -108,13 +98,20 @@ public class BrowserModel {
public void closeFileSystemAsync(OpenFileSystemModel open) { public void closeFileSystemAsync(OpenFileSystemModel open) {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
open.closeSync(); closeFileSystemSync(open);
synchronized (BrowserModel.this) {
openFileSystems.remove(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<? extends FileSystemStore> store, Function<OpenFileSystemModel, String> path, BooleanProperty externalBusy) { public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, Function<OpenFileSystemModel, String> path, BooleanProperty externalBusy) {
if (store == null) { if (store == null) {
return; return;

View file

@ -1,27 +1,40 @@
package io.xpipe.app.browser; package io.xpipe.app.browser;
import io.xpipe.app.core.AppCache; import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Value public interface BrowserSavedState {
@Jacksonized
@Builder
@Getter
public class BrowserSavedState {
static BrowserSavedState load() { static BrowserSavedState none() {
return AppCache.get("browser-state", BrowserSavedState.class, () -> { return new BrowserSavedState() {
return null; @Override
}); public void add(Entry entry) {
}
@Override
public void save() {
}
@Override
public ObservableList<Entry> getEntries() {
return FXCollections.observableArrayList();
}
};
} }
public void add(Entry entry);
void save();
ObservableList<Entry> getEntries();
@Value @Value
@Jacksonized @Jacksonized
@Builder @Builder
@ -30,10 +43,4 @@ public class BrowserSavedState {
UUID uuid; UUID uuid;
String path; String path;
} }
@NonNull List<Entry> lastSystems;
public void save() {
AppCache.update("browser-state", this);
}
} }

View file

@ -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<Entry> 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<Entry> getEntries() {
return lastSystems;
}
}

View file

@ -5,19 +5,21 @@ import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp; 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.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; 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.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.JfxHelper; import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
@ -33,33 +35,26 @@ public class BrowserWelcomeComp extends SimpleComp {
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var state = BrowserSavedState.load(); var state = model.getSavedState();
var welcome = new BrowserGreetingComp().createSimple(); 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 img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75).padding(new Insets(5, 0, 0, 0)).createRegion();
var hbox = new HBox(img, vbox); var hbox = new HBox(img, vbox);
hbox.setAlignment(Pos.CENTER_LEFT);
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 you exited XPipe."); 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); 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 header = new Label("Last time you were connected to the following systems:"); var list = BindingsHelper.filteredContentBinding(state.getEntries(), e -> {
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 entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
if (entry.isEmpty()) { if (entry.isEmpty()) {
return false; return false;
@ -70,8 +65,20 @@ public class BrowserWelcomeComp extends SimpleComp {
} }
return true; 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 entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore()); var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
var view = PrettyImageHelper.ofFixedSquare(graphic, 45); var view = PrettyImageHelper.ofFixedSquare(graphic, 45);
@ -83,26 +90,26 @@ public class BrowserWelcomeComp extends SimpleComp {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
model.restoreState(e, disable); 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 -> { }).apply(struc -> {
VBox vBox = (VBox) struc.get().getContent(); VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10); vBox.setSpacing(10);
}).createRegion(); }).hide(empty).createRegion();
var layout = new VBox(); var layout = new VBox();
layout.getStyleClass().add("welcome"); layout.getStyleClass().add("welcome");
layout.setPadding(new Insets(40, 40, 40, 50)); layout.setPadding(new Insets(40, 40, 40, 50));
layout.setSpacing(18); layout.setSpacing(18);
layout.getChildren().add(hbox); layout.getChildren().add(hbox);
layout.getChildren().add(new Separator(Orientation.HORIZONTAL)); layout.getChildren().add(Comp.separator().hide(empty).createRegion());
layout.getChildren().add(box); layout.getChildren().add(listBox);
VBox.setVgrow(layout.getChildren().get(2), Priority.NEVER); 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 -> { var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
model.restoreState(state); model.restoreState(state);
actionEvent.consume(); actionEvent.consume();
}).grow(true, false).accessibleTextKey("restoreAllSessions"); }).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions");
layout.getChildren().add(tile.createRegion()); layout.getChildren().add(tile.createRegion());
return layout; return layout;

View file

@ -41,7 +41,7 @@ public class StandaloneFileBrowser {
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) { public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> { 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) var comp = new BrowserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get())); .apply(struc -> AppFont.normal(struc.get()));
@ -57,7 +57,7 @@ public class StandaloneFileBrowser {
public static void saveSingleFile(Property<FileReference> file) { public static void saveSingleFile(Property<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> { 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) var comp = new BrowserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get())); .apply(struc -> AppFont.normal(struc.get()));

View file

@ -5,13 +5,11 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper; import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.ScanAlert; import io.xpipe.app.util.ScanAlert;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Separator; import javafx.scene.control.Separator;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
@ -26,7 +24,6 @@ public class StoreIntroComp extends SimpleComp {
public Region createSimple() { public Region createSimple() {
var title = new Label(AppI18n.get("storeIntroTitle")); var title = new Label(AppI18n.get("storeIntroTitle"));
AppFont.setSize(title, 7); AppFont.setSize(title, 7);
title.getStyleClass().add("title-header");
var introDesc = new Label(AppI18n.get("storeIntroDescription")); var introDesc = new Label(AppI18n.get("storeIntroDescription"));
@ -41,22 +38,10 @@ public class StoreIntroComp extends SimpleComp {
var scanPane = new StackPane(scanButton); var scanPane = new StackPane(scanButton);
scanPane.setAlignment(Pos.CENTER); scanPane.setAlignment(Pos.CENTER);
var dofi = new FontIcon("mdi2b-book-open-variant"); var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion();
var documentation = new Label(AppI18n.get("introDocumentation"), dofi); var text = new VBox(title, introDesc, new Separator(Orientation.HORIZONTAL), machine);
documentation.heightProperty().addListener((c, o, n) -> { text.setAlignment(Pos.CENTER_LEFT);
dofi.iconSizeProperty().set(n.intValue()); var hbox = new HBox(img, text);
});
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
));
hbox.setSpacing(35); hbox.setSpacing(35);
hbox.setAlignment(Pos.CENTER); hbox.setAlignment(Pos.CENTER);

View file

@ -289,13 +289,15 @@ public class BindingsHelper {
var newSet = new HashSet<>(newList); var newSet = new HashSet<>(newList);
// Only add missing element // 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); var l = new HashSet<>(newSet);
l.removeAll(targetSet); l.removeAll(targetSet);
var found = l.iterator().next(); if (l.size() > 0) {
var index = newList.indexOf(found); var found = l.iterator().next();
target.add(index, found); var index = newList.indexOf(found);
return; target.add(index, found);
return;
}
} }
// Only remove not needed element // Only remove not needed element

View file

@ -58,6 +58,7 @@ public class JfxHelper {
var desc = new Label(descString); var desc = new Label(descString);
AppFont.small(desc); AppFont.small(desc);
var text = new VBox(header, new Spacer(), desc); var text = new VBox(header, new Spacer(), desc);
text.setAlignment(Pos.CENTER_LEFT);
if (image == null) { if (image == null) {
return text; return text;