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(),
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 -> {

View file

@ -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;
}
}

View file

@ -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<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final BrowserSavedState savedState;
@Setter
private Consumer<List<FileReference>> 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<BrowserSavedState.Entry>();
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<? extends FileSystemStore> store, Function<OpenFileSystemModel, String> path, BooleanProperty externalBusy) {
if (store == null) {
return;

View file

@ -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<Entry> getEntries() {
return FXCollections.observableArrayList();
}
};
}
public void add(Entry entry);
void save();
ObservableList<Entry> getEntries();
@Value
@Jacksonized
@Builder
@ -30,10 +43,4 @@ public class BrowserSavedState {
UUID uuid;
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.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;

View file

@ -41,7 +41,7 @@ public class StandaloneFileBrowser {
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> 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<FileReference> 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()));

View file

@ -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);

View file

@ -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

View file

@ -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;