From 169f8449669eeea08b28deefeb084a95fa8dff63 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 28 Jun 2025 05:45:07 +0000 Subject: [PATCH] Rework --- .../io/xpipe/app/hub/comp/StoreEntryComp.java | 36 ++++++++++++++++--- .../xpipe/app/hub/comp/StoreEntryWrapper.java | 19 ++++++++++ .../app/hub/comp/StoreSectionSortMode.java | 14 ++++---- .../io/xpipe/app/storage/DataStorage.java | 4 +++ .../io/xpipe/app/storage/DataStoreEntry.java | 22 ++++++++++++ lang/strings/translations_en.properties | 13 ++++--- 6 files changed, 91 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java index 1a43251d4..3da5bfd7b 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java @@ -454,7 +454,7 @@ public abstract class StoreEntryComp extends SimpleComp { } { - var order = new Menu(AppI18n.get("order"), new FontIcon("mdi2b-bookmark-multiple-outline")); + var order = new Menu(AppI18n.get("order"), new FontIcon("mdi2f-format-list-bulleted")); var index = new MenuItem(AppI18n.get("index"), new FontIcon("mdi2o-order-numeric-ascending")); index.setOnAction(event -> { @@ -475,14 +475,14 @@ public abstract class StoreEntryComp extends SimpleComp { order.getItems().add(noOrder); } - var first = new MenuItem(AppI18n.get("moveToTop"), new FontIcon("mdi2o-order-bool-descending")); + var first = new MenuItem(AppI18n.get("moveToFirst"), new FontIcon("mdi2o-order-bool-descending")); first.setOnAction(event -> { getWrapper().orderFirst(); event.consume(); }); order.getItems().add(first); - var last = new MenuItem(AppI18n.get("moveToBottom"), new FontIcon("mdi2o-order-bool-ascending")); + var last = new MenuItem(AppI18n.get("moveToLast"), new FontIcon("mdi2o-order-bool-ascending")); last.setOnAction(event -> { getWrapper().orderLast(); event.consume(); @@ -492,7 +492,7 @@ public abstract class StoreEntryComp extends SimpleComp { order.getItems().add(new SeparatorMenuItem()); var top = new MenuItem( - AppI18n.get("stickToTop"), new FontIcon("mdi2o-order-bool-descending-variant")); + AppI18n.get("keepFirst"), new FontIcon("mdi2o-order-bool-descending-variant")); top.setOnAction(event -> { getWrapper().orderStickFirst(); event.consume(); @@ -501,7 +501,7 @@ public abstract class StoreEntryComp extends SimpleComp { order.getItems().add(top); var bottom = new MenuItem( - AppI18n.get("stickToBottom"), new FontIcon("mdi2o-order-bool-ascending-variant")); + AppI18n.get("keepLast"), new FontIcon("mdi2o-order-bool-ascending-variant")); bottom.setOnAction(event -> { getWrapper().orderStickLast(); event.consume(); @@ -538,6 +538,32 @@ public abstract class StoreEntryComp extends SimpleComp { }); items.add(move); } + + if (getWrapper().getPinToTop().getValue() || section.getDepth() > 1) { + var pinToTop = new MenuItem(); + pinToTop.graphicProperty() + .bind(Bindings.createObjectBinding( + () -> { + var is = getWrapper().getPinToTop().get(); + return is + ? new FontIcon("mdi2p-pin-off-outline") + : new FontIcon("mdi2p-pin-outline"); + }, + getWrapper().getPinToTop())); + pinToTop.textProperty() + .bind(Bindings.createStringBinding( + () -> { + var is = getWrapper().getPinToTop().get(); + return is + ? AppI18n.get("unpinFromTop") + : AppI18n.get("pinToTop"); + }, + AppI18n.activeLanguage(), + getWrapper().getPinToTop())); + pinToTop.setOnAction(event -> getWrapper() + .togglePinToTop()); + items.add(pinToTop); + } } if (cat == StoreActionCategory.DELETION) { diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java index 763b23e3b..fc882f949 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java @@ -64,6 +64,7 @@ public class StoreEntryWrapper { private final BooleanProperty largeCategoryOptimizations = new SimpleBooleanProperty(); private final BooleanProperty readOnly = new SimpleBooleanProperty(); private final BooleanProperty renaming = new SimpleBooleanProperty(); + private final BooleanProperty pinToTop = new SimpleBooleanProperty(); private final IntegerProperty orderIndex = new SimpleIntegerProperty(); private boolean effectiveBusyProviderBound = false; @@ -197,6 +198,7 @@ public class StoreEntryWrapper { perUser.setValue( !category.getValue().getRoot().equals(StoreViewState.get().getAllIdentitiesCategory()) && entry.isPerUserStore()); + pinToTop.setValue(entry.isPinToTop()); var storeChanged = store.getValue() != entry.getStore(); store.setValue(entry.getStore()); @@ -416,6 +418,23 @@ public class StoreEntryWrapper { this.expanded.set(!expanded.getValue()); } + public void togglePinToTop() { + if (getEntry().isPinToTop()) { + getEntry().setPinToTop(false); + StoreViewState.get().triggerStoreListUpdate(); + } else { + var root = StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList().stream().filter( + storeSection -> storeSection.anyMatches(storeEntryWrapper -> storeEntryWrapper == this)).findFirst(); + var sortMode = StoreSectionSortMode.DATE_DESC; + var date = root.isPresent() ? sortMode.date(sortMode.getRepresentative(root.get())).plus(Duration.ofSeconds(1)) : Instant.now(); + getEntry().setPinToTop(!getEntry().isPinToTop()); + StoreViewState.get().triggerStoreListUpdate(); + getEntry().setLastUsed(date); + getEntry().setLastModified(date); + StoreViewState.get().triggerStoreListUpdate(); + } + } + public boolean matchesFilter(String filter) { if (filter == null || name.getValue().toLowerCase().contains(filter.toLowerCase())) { return true; diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionSortMode.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionSortMode.java index 329a28e17..80331b5f0 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionSortMode.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionSortMode.java @@ -57,9 +57,9 @@ public interface StoreSectionSortMode { .reversed(); } }; - StoreSectionSortMode DATE_DESC = new StoreSectionSortMode.DateSortMode() { + StoreSectionSortMode.DateSortMode DATE_DESC = new StoreSectionSortMode.DateSortMode() { - protected Instant date(StoreSection s) { + public Instant date(StoreSection s) { var la = s.getWrapper().getLastAccess().getValue(); if (la == null) { return Instant.MAX; @@ -78,9 +78,9 @@ public interface StoreSectionSortMode { return "date-desc"; } }; - StoreSectionSortMode DATE_ASC = new StoreSectionSortMode.DateSortMode() { + StoreSectionSortMode.DateSortMode DATE_ASC = new StoreSectionSortMode.DateSortMode() { - protected Instant date(StoreSection s) { + public Instant date(StoreSection s) { var la = s.getWrapper().getLastAccess().getValue(); if (la == null) { return Instant.MIN; @@ -117,7 +117,7 @@ public interface StoreSectionSortMode { private int entriesListObservableIndex = -1; private final Map cachedRepresentatives = new IdentityHashMap<>(); - private StoreSection computeRepresentative(StoreSection s) { + public StoreSection computeRepresentative(StoreSection s) { return Stream.concat( s.getShownChildren().getList().stream() .filter(section -> section.getWrapper() @@ -130,7 +130,7 @@ public interface StoreSectionSortMode { .orElseThrow(); } - private StoreSection getRepresentative(StoreSection s) { + public StoreSection getRepresentative(StoreSection s) { if (StoreViewState.get().getEntriesListUpdateObservable().get() != entriesListObservableIndex) { cachedRepresentatives.clear(); entriesListObservableIndex = @@ -146,7 +146,7 @@ public interface StoreSectionSortMode { return r; } - protected abstract Instant date(StoreSection s); + public abstract Instant date(StoreSection s); protected abstract int compare(Instant s1, Instant s2); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index ad95cd060..bb54016b4 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -862,6 +862,10 @@ public abstract class DataStorage { // Get operations public boolean isRootEntry(DataStoreEntry entry, DataStoreCategory current) { + if (entry.isPinToTop()) { + return true; + } + var parent = getDefaultDisplayParent(entry); var noParent = parent.isEmpty(); if (noParent) { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 746ee9910..3c919e390 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -79,6 +79,10 @@ public class DataStoreEntry extends StorageElement { @Getter boolean freeze; + @NonFinal + @Getter + boolean pinToTop; + @Getter @NonFinal int orderIndex; @@ -100,6 +104,7 @@ public class DataStoreEntry extends StorageElement { String notes, String icon, boolean freeze, + boolean pinToTop, int orderIndex) { super(directory, uuid, name, lastUsed, lastModified, expanded, dirty); this.color = color; @@ -112,6 +117,7 @@ public class DataStoreEntry extends StorageElement { this.notes = notes; this.icon = icon; this.freeze = freeze; + this.pinToTop = pinToTop; this.orderIndex = orderIndex; } @@ -133,6 +139,7 @@ public class DataStoreEntry extends StorageElement { null, null, false, + false, 0); } @@ -171,6 +178,7 @@ public class DataStoreEntry extends StorageElement { null, null, false, + false, 0); return entry; } @@ -228,6 +236,9 @@ public class DataStoreEntry extends StorageElement { var freeze = Optional.ofNullable(json.get("freeze")) .map(jsonNode -> jsonNode.booleanValue()) .orElse(false); + var pinToTop = Optional.ofNullable(json.get("pinToTop")) + .map(jsonNode -> jsonNode.booleanValue()) + .orElse(false); var iconNode = json.get("icon"); String icon = iconNode != null && !iconNode.isNull() ? iconNode.asText() : null; @@ -305,6 +316,7 @@ public class DataStoreEntry extends StorageElement { notes, icon, freeze, + pinToTop, orderIndex)); } @@ -464,6 +476,7 @@ public class DataStoreEntry extends StorageElement { obj.set("color", mapper.valueToTree(color)); obj.set("icon", mapper.valueToTree(icon)); obj.put("freeze", freeze); + obj.put("pinToTop", pinToTop); obj.put("orderIndex", orderIndex); ObjectNode stateObj = JsonNodeFactory.instance.objectNode(); @@ -519,6 +532,15 @@ public class DataStoreEntry extends StorageElement { } } + public void setPinToTop(boolean newValue) { + var changed = pinToTop != newValue; + this.pinToTop = newValue; + if (changed) { + notifyUpdate(false, false); + dirty = true; + } + } + public boolean isDisabled() { return validity == Validity.LOAD_FAILED; } diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index dce6c902f..d8ab9e1c0 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -442,9 +442,12 @@ skipAll=Skip all notes=Notes addNotes=Add notes #context: verb, to reorder -order=Order ... -stickToTop=Keep on top -stickToBottom=Keep on bottom +#force +order=Order +keepFirst=Keep first +keepLast=Keep last +pinToTop=Pin to top +unpinFromTop=Unpin from top orderAheadOf=Order ahead of ... httpServer=HTTP server httpServerConfiguration=HTTP server configuration @@ -1525,8 +1528,8 @@ appleContainers=Apple containers changeOrderIndexTitle=Change order index orderIndex=Index orderIndexDescription=Explicit index to order this entry relative to others. Lowest indices are shown on top, highest on the bottom -moveToBottom=Move to bottom -moveToTop=Move to top +moveToFirst=Move to first +moveToLast=Move to last category=Category includeRoot=Include root excludeRoot=Exclude root