mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-06-24 17:10:19 +12:00
Improve hierarchical store display [release]
This commit is contained in:
parent
add30cb413
commit
849e8f0ef4
|
@ -1,8 +0,0 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
|
||||
record Bookmark(DataStoreEntry entry) {
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryFlatMiniSection;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
final class BookmarkList extends SimpleComp {
|
||||
|
||||
|
@ -22,19 +20,23 @@ final class BookmarkList extends SimpleComp {
|
|||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var list = DataStorage.get().getStoreEntries().stream().filter(entry -> entry.getStore() instanceof ShellStore).map(entry -> new Bookmark(entry)).toList();
|
||||
return new ListBoxViewComp<>(FXCollections.observableList(list), FXCollections.observableList(list), bookmark -> {
|
||||
var imgView =
|
||||
new PrettyImageComp(new SimpleStringProperty(bookmark.entry().getProvider().getDisplayIconFileName()), 16, 16).createRegion();
|
||||
var button = new JFXButton(bookmark.entry().getName(), imgView);
|
||||
button.setOnAction(event -> {
|
||||
event.consume();
|
||||
var map = StoreEntryFlatMiniSection.createMap();
|
||||
var list = new VBox();
|
||||
for (Map.Entry<StoreEntryWrapper, Region> e : map.entrySet()) {
|
||||
if (!(e.getKey().getEntry().getStore() instanceof ShellStore)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileSystem = ((ShellStore) bookmark.entry().getStore());
|
||||
var button = new JFXButton(null, e.getValue());
|
||||
button.setOnAction(event -> {
|
||||
var fileSystem = ((ShellStore) e.getKey().getEntry().getStore());
|
||||
model.openFileSystem(fileSystem);
|
||||
event.consume();
|
||||
});
|
||||
button.setAlignment(Pos.CENTER_LEFT);
|
||||
return Comp.of(() -> button).grow(true, false);
|
||||
}).createRegion();
|
||||
button.prefWidthProperty().bind(list.widthProperty());
|
||||
list.getChildren().add(button);
|
||||
}
|
||||
list.setFillWidth(true);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class StoreEntryFlatMiniSection extends SimpleComp {
|
||||
|
||||
public static Map<StoreEntryWrapper, Region> createMap() {
|
||||
var map = new LinkedHashMap<StoreEntryWrapper, Region>();
|
||||
var topLevel = StoreViewSection.createTopLevels();
|
||||
var depth = 0;
|
||||
for (StoreViewSection v : topLevel) {
|
||||
add(depth, v, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void add(int depth, StoreViewSection section, Map<StoreEntryWrapper, Region> map) {
|
||||
map.put(section.getEntry(), new StoreEntryFlatMiniSection(depth, section.getEntry()).createRegion());
|
||||
for (StoreViewSection child : section.getChildren()) {
|
||||
add(depth + 1, child, map);
|
||||
}
|
||||
}
|
||||
|
||||
int depth;
|
||||
StoreEntryWrapper wrapper;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var label = new Label(wrapper.getName(), new PrettyImageComp(new SimpleStringProperty(wrapper.getEntry().getProvider().getDisplayIconFileName()), 20, 20).createRegion());
|
||||
var spacer = new Spacer(depth * 10, Orientation.HORIZONTAL);
|
||||
var box = new HBox(spacer, label);
|
||||
return box;
|
||||
}
|
||||
}
|
|
@ -15,14 +15,14 @@ import java.util.LinkedHashMap;
|
|||
public class StoreEntryListComp extends SimpleComp {
|
||||
|
||||
private Comp<?> createList() {
|
||||
var topLevel = StoreEntrySection.createTopLevels();
|
||||
var topLevel = StoreViewSection.createTopLevels();
|
||||
var filtered = BindingsHelper.filteredContentBinding(
|
||||
topLevel,
|
||||
StoreViewState.get()
|
||||
.getFilterString()
|
||||
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
||||
var content = new ListBoxViewComp<>(filtered, topLevel, (StoreEntrySection e) -> {
|
||||
return e.comp(true);
|
||||
var content = new ListBoxViewComp<>(filtered, topLevel, (StoreViewSection e) -> {
|
||||
return new StoreEntrySection(e, true);
|
||||
});
|
||||
return content.styleClass("store-list-comp").styleClass(Styles.STRIPED);
|
||||
}
|
||||
|
|
|
@ -1,85 +1,32 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.storage.StorageFilter;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class StoreEntrySection implements StorageFilter.Filterable {
|
||||
public class StoreEntrySection extends Comp<CompStructure<VBox>> {
|
||||
|
||||
private static final Comparator<StoreEntrySection> COMPARATOR = Comparator.<StoreEntrySection, Instant>comparing(
|
||||
o -> o.entry.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
|
||||
? o.entry.getEntry().getLastAccess()
|
||||
: Instant.EPOCH).reversed()
|
||||
.thenComparing(
|
||||
storeEntrySection -> storeEntrySection.entry.getEntry().getName());
|
||||
private final StoreViewSection section;
|
||||
private final boolean top;
|
||||
|
||||
public StoreEntrySection(StoreEntryWrapper entry, ObservableList<StoreEntrySection> children) {
|
||||
this.entry = entry;
|
||||
this.children = children;
|
||||
public StoreEntrySection(StoreViewSection section, boolean top) {
|
||||
this.section = section;
|
||||
this.top = top;
|
||||
}
|
||||
|
||||
public static ObservableList<StoreEntrySection> createTopLevels() {
|
||||
var filtered =
|
||||
BindingsHelper.filteredContentBinding(StoreViewState.get().getAllEntries(), storeEntryWrapper -> {
|
||||
if (!storeEntryWrapper.getEntry().getState().isUsable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var parent = storeEntryWrapper
|
||||
.getEntry()
|
||||
.getProvider()
|
||||
.getParent(storeEntryWrapper.getEntry().getStore());
|
||||
return parent == null
|
||||
|| (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
|
||||
});
|
||||
var topLevel = BindingsHelper.mappedContentBinding(filtered, storeEntryWrapper -> create(storeEntryWrapper));
|
||||
var ordered = BindingsHelper.orderedContentBinding(
|
||||
topLevel,
|
||||
COMPARATOR);
|
||||
return ordered;
|
||||
}
|
||||
|
||||
public static StoreEntrySection create(StoreEntryWrapper e) {
|
||||
if (!e.getEntry().getState().isUsable()) {
|
||||
return new StoreEntrySection(e, FXCollections.observableArrayList());
|
||||
}
|
||||
|
||||
var filtered = BindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
other -> other.getEntry().getState().isUsable()
|
||||
&& e.getEntry()
|
||||
.getStore()
|
||||
.equals(other.getEntry()
|
||||
.getProvider()
|
||||
.getParent(other.getEntry().getStore())));
|
||||
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
|
||||
var ordered = BindingsHelper.orderedContentBinding(
|
||||
children,
|
||||
COMPARATOR);
|
||||
return new StoreEntrySection(e, ordered);
|
||||
}
|
||||
|
||||
private final StoreEntryWrapper entry;
|
||||
private final ObservableList<StoreEntrySection> children;
|
||||
|
||||
public Comp<?> comp(boolean top) {
|
||||
var root = new StoreEntryComp(entry).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var root = new StoreEntryComp(section.getEntry()).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var icon = Comp.of(() -> {
|
||||
var padding = new FontIcon("mdal-arrow_forward_ios");
|
||||
padding.setIconSize(14);
|
||||
|
@ -90,15 +37,15 @@ public class StoreEntrySection implements StorageFilter.Filterable {
|
|||
});
|
||||
List<Comp<?>> topEntryList = top ? List.of(root) : List.of(icon, root);
|
||||
|
||||
var all = children;
|
||||
var all = section.getChildren();
|
||||
var shown = BindingsHelper.filteredContentBinding(
|
||||
all,
|
||||
StoreViewState.get()
|
||||
.getFilterString()
|
||||
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
||||
var content = new ListBoxViewComp<>(shown, all, (StoreEntrySection e) -> {
|
||||
return e.comp(false).apply(GrowAugment.create(true, false));
|
||||
})
|
||||
var content = new ListBoxViewComp<>(shown, all, (StoreViewSection e) -> {
|
||||
return new StoreEntrySection(e, false).apply(GrowAugment.create(true, false));
|
||||
})
|
||||
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
|
||||
.apply(struc -> struc.get().backgroundProperty().set(Background.fill(Color.color(0, 0, 0, 0.01))));
|
||||
var spacer = Comp.of(() -> {
|
||||
|
@ -111,12 +58,6 @@ public class StoreEntrySection implements StorageFilter.Filterable {
|
|||
new HorizontalComp(topEntryList),
|
||||
new HorizontalComp(List.of(spacer, content))
|
||||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.hide(BindingsHelper.persist(Bindings.size(children).isEqualTo(0)))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShow(String filter) {
|
||||
return entry.shouldShow(filter)
|
||||
|| children.stream().anyMatch(storeEntrySection -> storeEntrySection.shouldShow(filter));
|
||||
.hide(BindingsHelper.persist(Bindings.size(section.getChildren()).isEqualTo(0))))).createStructure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.comp.storage.StorageFilter;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Value;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
|
||||
@Value
|
||||
public class StoreViewSection implements StorageFilter.Filterable {
|
||||
|
||||
StoreEntryWrapper entry;
|
||||
ObservableList<StoreViewSection> children;
|
||||
|
||||
private static final Comparator<StoreViewSection> COMPARATOR = Comparator.<StoreViewSection, Instant>comparing(
|
||||
o -> o.entry.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
|
||||
? o.entry.getEntry().getLastAccess()
|
||||
: Instant.EPOCH).reversed()
|
||||
.thenComparing(
|
||||
storeEntrySection -> storeEntrySection.entry.getEntry().getName());
|
||||
|
||||
public static ObservableList<StoreViewSection> createTopLevels() {
|
||||
var filtered =
|
||||
BindingsHelper.filteredContentBinding(StoreViewState.get().getAllEntries(), storeEntryWrapper -> {
|
||||
if (!storeEntryWrapper.getEntry().getState().isUsable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var parent = storeEntryWrapper
|
||||
.getEntry()
|
||||
.getProvider()
|
||||
.getParent(storeEntryWrapper.getEntry().getStore());
|
||||
return parent == null
|
||||
|| (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
|
||||
});
|
||||
var topLevel = BindingsHelper.mappedContentBinding(filtered, storeEntryWrapper -> create(storeEntryWrapper));
|
||||
var ordered = BindingsHelper.orderedContentBinding(
|
||||
topLevel,
|
||||
COMPARATOR);
|
||||
return ordered;
|
||||
}
|
||||
|
||||
public static StoreViewSection create(StoreEntryWrapper e) {
|
||||
if (!e.getEntry().getState().isUsable()) {
|
||||
return new StoreViewSection(e, FXCollections.observableArrayList());
|
||||
}
|
||||
|
||||
var filtered = BindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
other -> other.getEntry().getState().isUsable()
|
||||
&& e.getEntry()
|
||||
.getStore()
|
||||
.equals(other.getEntry()
|
||||
.getProvider()
|
||||
.getParent(other.getEntry().getStore())));
|
||||
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
|
||||
var ordered = BindingsHelper.orderedContentBinding(
|
||||
children,
|
||||
COMPARATOR);
|
||||
return new StoreViewSection(e, ordered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShow(String filter) {
|
||||
return entry.shouldShow(filter)
|
||||
|| children.stream().anyMatch(storeEntrySection -> storeEntrySection.shouldShow(filter));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryFlatMiniSection;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
|
@ -15,6 +17,7 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.layout.Region;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
@ -71,17 +74,31 @@ public class ShellStoreChoiceComp<T extends ShellStore> extends SimpleComp {
|
|||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Region createSimple() {
|
||||
var comboBox =
|
||||
new CustomComboBoxBuilder<T>(selected, this::createGraphic, new Label(AppI18n.get("none")), n -> true);
|
||||
var map = StoreEntryFlatMiniSection.createMap();
|
||||
var comboBox = new CustomComboBoxBuilder<T>(
|
||||
selected,
|
||||
t -> map.entrySet().stream()
|
||||
.filter(e -> t.equals(e.getKey().getEntry().getStore()))
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getValue(),
|
||||
new Label(AppI18n.get("none")),
|
||||
n -> true);
|
||||
comboBox.setUnknownNode(t -> createGraphic(t));
|
||||
|
||||
var available = DataStorage.get().getUsableStores().stream()
|
||||
.filter(s -> s != self)
|
||||
.filter(s -> storeClass.isAssignableFrom(s.getClass()) && applicableCheck.test((T) s))
|
||||
.map(s -> (ShellStore) s)
|
||||
.toList();
|
||||
for (Map.Entry<StoreEntryWrapper, Region> e : map.entrySet()) {
|
||||
if (e.getKey().getEntry().getStore() == self) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var s = e.getKey().getEntry().getStore();
|
||||
if (!storeClass.isAssignableFrom(s.getClass()) || !applicableCheck.test((T) s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
comboBox.add((T) e.getKey().getEntry().getStore());
|
||||
}
|
||||
|
||||
available.forEach(s -> comboBox.add((T) s));
|
||||
ComboBox<Node> cb = comboBox.build();
|
||||
cb.getStyleClass().add("choice-comp");
|
||||
cb.setMaxWidth(2000);
|
||||
|
|
Loading…
Reference in a new issue