Merge branch tags

This commit is contained in:
crschnick
2025-12-16 08:11:21 +00:00
parent 15e9172c39
commit e8b5b85e5c
8 changed files with 152 additions and 12 deletions
@@ -69,6 +69,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
var grid = new GridPane();
grid.setHgap(8);
var tags = createTags().createRegion();
var index = createOrderIndex().createRegion();
var name = createName().createRegion();
name.maxWidthProperty()
@@ -111,7 +112,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
grid.getColumnConstraints().addAll(nameCC);
var active = new StoreActiveComp(getWrapper()).createRegion();
var nameBox = new HBox(name, index, userIcon, pinIcon, notes);
var nameBox = new HBox(name, tags, index, userIcon, pinIcon, notes);
getWrapper().getSessionActive().subscribe(aBoolean -> {
if (!aBoolean) {
nameBox.getChildren().remove(active);
@@ -119,7 +120,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
nameBox.getChildren().add(1, active);
}
});
nameBox.setSpacing(6);
nameBox.setSpacing(4);
nameBox.setAlignment(Pos.CENTER_LEFT);
grid.addRow(0, nameBox);
@@ -30,6 +30,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
protected Region createContent() {
var name = createName().createRegion();
var tags = createTags().createRegion();
var index = createOrderIndex().createRegion();
var notes = new StoreNotesComp(getWrapper()).createRegion();
var userIcon = createUserIcon().createRegion();
@@ -55,8 +56,8 @@ public class StandardStoreEntryComp extends StoreEntryComp {
grid.getColumnConstraints().add(new ColumnConstraints(52));
var active = new StoreActiveComp(getWrapper()).createRegion();
var nameBox = new HBox(name, index, userIcon, pinIcon, notes);
nameBox.setSpacing(6);
var nameBox = new HBox(name, tags, index, userIcon, pinIcon, notes);
nameBox.setSpacing(4);
nameBox.setAlignment(Pos.CENTER_LEFT);
grid.add(nameBox, 2, 0);
GridPane.setVgrow(nameBox, Priority.ALWAYS);
@@ -1,6 +1,7 @@
package io.xpipe.app.hub.comp;
import io.xpipe.app.action.ActionProvider;
import io.xpipe.app.browser.action.impl.ChmodActionProvider;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.comp.augment.ContextMenuAugment;
@@ -19,12 +20,10 @@ import io.xpipe.app.util.*;
import io.xpipe.core.OsType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.*;
import javafx.beans.value.ObservableDoubleValue;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -238,6 +237,17 @@ public abstract class StoreEntryComp extends SimpleComp {
return name;
}
protected Comp<?> createTags() {
var tagsProp = Bindings.createStringBinding(() -> {
return getWrapper().getTags().stream().map(s -> {
return "[" + s + "]";
}).collect(Collectors.joining(" "));
}, getWrapper().getTags());
var tagsLabel = new LabelComp(tagsProp);
tagsLabel.apply(struc -> struc.get().setOpacity(0.85));
return tagsLabel;
}
protected Comp<?> createOrderIndex() {
var prop = new SimpleStringProperty();
getWrapper().getOrderIndex().subscribe(number -> {
@@ -247,6 +257,7 @@ public abstract class StoreEntryComp extends SimpleComp {
});
var comp = new LabelComp(prop);
comp.styleClass("orderIndex");
comp.apply(struc -> struc.get().setOpacity(0.85));
return comp;
}
@@ -260,6 +271,7 @@ public abstract class StoreEntryComp extends SimpleComp {
struc.get().setOpacity(1.0);
});
button.hide(Bindings.not(getWrapper().getPerUser()));
button.apply(struc -> struc.get().setOpacity(0.85));
return button;
}
@@ -272,6 +284,7 @@ public abstract class StoreEntryComp extends SimpleComp {
struc.get().setOpacity(1.0);
});
button.hide(Bindings.not(getWrapper().getPinToTop()));
button.apply(struc -> struc.get().setOpacity(0.85));
return button;
}
@@ -502,6 +515,48 @@ public abstract class StoreEntryComp extends SimpleComp {
}
{
var tags = new Menu(AppI18n.get("tags"), new FontIcon("mdi2t-tag-text-outline"));
var allTags = StoreViewState.get().getAllAvailableTags();
for (String tag : allTags) {
var tagItem = new MenuItem(tag);
if (getWrapper().getTags().contains(tag)) {
tagItem.setGraphic(new FontIcon("mdi2c-check"));
}
tagItem.addEventFilter(ActionEvent.ACTION, event -> {
getWrapper().toggleTag(tag);
event.consume();
});
tags.getItems().add(tagItem);
}
if (allTags.size() > 0) {
tags.getItems().add(new SeparatorMenuItem());
}
var index = ContextMenuHelper.item(new LabelGraphic.IconGraphic("mdi2t-tag-plus-outline"), "createTag");
index.setOnAction(event -> {
var tagName = new SimpleStringProperty();
var modal = ModalOverlay.of(
"addNewTag",
Comp.of(() -> {
var creationName = new TextField();
creationName.textProperty().bindBidirectional(tagName);
return creationName;
})
.prefWidth(350));
modal.withDefaultButtons(() -> {
getWrapper().getEntry().addTag(tagName.getValue());
});
modal.show();
event.consume();
});
tags.getItems().add(index);
items.add(tags);
}
{
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"));
@@ -13,6 +13,8 @@ import io.xpipe.app.hub.action.HubLeafProvider;
import io.xpipe.app.hub.action.HubMenuItemProvider;
import io.xpipe.app.hub.action.impl.EditHubLeafProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.DerivedObservableList;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
@@ -27,6 +29,7 @@ import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.time.Duration;
@@ -71,6 +74,7 @@ public class StoreEntryWrapper {
private final BooleanProperty pinToTop = new SimpleBooleanProperty();
private final IntegerProperty orderIndex = new SimpleIntegerProperty();
private final BooleanProperty effectiveBusy = new SimpleBooleanProperty();
private final ObservableList<String> tags = FXCollections.observableArrayList();
private boolean effectiveBusyProviderBound = false;
public StoreEntryWrapper(DataStoreEntry entry) {
@@ -212,6 +216,9 @@ public class StoreEntryWrapper {
&& entry.isPerUserStore());
pinToTop.setValue(entry.isPinToTop());
var orderedTags = entry.getTags().stream().sorted().toList();
DerivedObservableList.wrap(tags, true).setContent(orderedTags);
var storeChanged = store.getValue() != entry.getStore();
store.setValue(entry.getStore());
if (storeChanged || !information.isBound()) {
@@ -400,6 +407,14 @@ public class StoreEntryWrapper {
return Optional.of(StoreViewState.get().getCategoryWrapper(cat.get()));
}
public void toggleTag(String tag) {
if (tags.contains(tag)) {
entry.removeTag(tag);
} else {
entry.addTag(tag);
}
}
public void mergeBreakOutCategory() {
ThreadHelper.runAsync(() -> {
DataStorage.get().mergeBreakOutCategory(entry);
@@ -539,6 +554,10 @@ public class StoreEntryWrapper {
return true;
}
if (tags.stream().anyMatch(s -> s.toLowerCase().contains(filter.toLowerCase()))) {
return true;
}
return false;
}
@@ -42,6 +42,7 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
.hide(BindingsHelper.map(n, s -> s.getCommited() == null && s.getCurrent() == null))
.createStructure()
.get();
button.setOpacity(0.85);
button.prefWidthProperty().bind(button.heightProperty());
var prop = new SimpleStringProperty(n.getValue().getCurrent());
@@ -152,6 +152,14 @@ public class StoreViewState {
return INSTANCE;
}
public List<String> getAllAvailableTags() {
var l = new LinkedHashSet<String>();
for (StoreEntryWrapper storeEntryWrapper : getAllEntries().getList()) {
l.addAll(storeEntryWrapper.getTags());
}
return l.stream().sorted().toList();
}
public ObservableIntegerValue entriesCount(Predicate<StoreEntryWrapper> filter, Observable... observables) {
return Bindings.size(allEntries
.filtered(
@@ -88,6 +88,8 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
UUID breakOutCategory;
List<String> tags;
private DataStoreEntry(
Path directory,
UUID uuid,
@@ -107,7 +109,8 @@ public class DataStoreEntry extends StorageElement {
boolean freeze,
boolean pinToTop,
int orderIndex,
UUID breakOutCategory) {
UUID breakOutCategory, List<String> tags
) {
super(directory, uuid, name, lastUsed, lastModified, expanded, dirty);
this.color = color;
this.categoryUuid = categoryUuid;
@@ -123,6 +126,7 @@ public class DataStoreEntry extends StorageElement {
this.pinToTop = pinToTop;
this.orderIndex = orderIndex;
this.breakOutCategory = breakOutCategory;
this.tags = tags;
}
public static DataStoreEntry createTempWrapper(@NonNull DataStore store) {
@@ -147,7 +151,8 @@ public class DataStoreEntry extends StorageElement {
false,
false,
0,
null);
null,
new ArrayList<>());
}
public static DataStoreEntry createNew(@NonNull NameableStore store) {
@@ -191,7 +196,8 @@ public class DataStoreEntry extends StorageElement {
false,
false,
0,
null);
null,
new ArrayList<>());
return entry;
}
@@ -243,6 +249,18 @@ public class DataStoreEntry extends StorageElement {
var pinToTop = Optional.ofNullable(json.get("pinToTop"))
.map(jsonNode -> jsonNode.booleanValue())
.orElse(false);
var tags = Optional.ofNullable(json.get("tags"))
.map(jsonNode -> {
List<String> l = new ArrayList<>();
for (JsonNode node : jsonNode) {
var tag = node.asText();
if (!tag.isBlank()) {
l.add(tag);
}
}
return l;
})
.orElse(new ArrayList<>());
var iconNode = json.get("icon");
String icon = iconNode != null && !iconNode.isNull() ? iconNode.asText() : null;
@@ -322,7 +340,8 @@ public class DataStoreEntry extends StorageElement {
freeze,
pinToTop,
orderIndex,
breakOutCategory));
breakOutCategory,
tags));
}
public String getEffectiveIconFile() {
@@ -471,6 +490,33 @@ public class DataStoreEntry extends StorageElement {
}
}
public void addTag(String tag) {
if (tags == null || tag.isBlank()) {
return;
}
tag = tag.strip();
if (tags.contains(tag)) {
return;
}
tags.add(tag);
notifyUpdate(false, true);
}
public void removeTag(String tag) {
if (tags == null || tag.isBlank()) {
return;
}
tag = tag.strip();
if (tags.remove(tag)) {
notifyUpdate(false, true);
}
}
public void setCategoryUuid(UUID categoryUuid) {
var changed = !Objects.equals(this.categoryUuid, categoryUuid);
this.categoryUuid = categoryUuid;
@@ -510,6 +556,11 @@ public class DataStoreEntry extends StorageElement {
obj.put("pinToTop", pinToTop);
obj.put("orderIndex", orderIndex);
var tagsArray = obj.putArray("tags");
for (String tag : tags) {
tagsArray.add(tag);
}
ObjectNode stateObj = JsonNodeFactory.instance.objectNode();
stateObj.put("lastUsed", lastUsed.toString());
stateObj.put("lastModified", lastModified.toString());
+4
View File
@@ -1699,3 +1699,7 @@ tunnelToLocalhostDescription=Automatically tunnel the remote port to localhost
#context: SSH jump server, noun
useGatewayAsJumpServer=Use gateway as jump server
useGatewayAsJumpServerDescription=Use the gateway as a jump server with ProxyJump
tags=Tags
tag=Tag
addNewTag=Create new tag
createTag=Create tag ...