mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-09-27 10:55:52 +00:00
Merge branch tags
This commit is contained in:
@@ -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());
|
||||
|
||||
Generated
+4
@@ -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 ...
|
||||
|
||||
Reference in New Issue
Block a user