Add support for connection colors

This commit is contained in:
crschnick 2023-10-06 20:41:01 +00:00
parent 45e3323828
commit 993e1d5446
9 changed files with 295 additions and 51 deletions

View file

@ -17,7 +17,9 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.DesktopShortcuts;
import io.xpipe.app.util.ThreadHelper;
@ -43,13 +45,14 @@ import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.util.ArrayList;
import java.util.Arrays;
public abstract class StoreEntryComp extends SimpleComp {
public static StoreEntryComp create(
StoreEntryWrapper entry, boolean showIcon, Comp<?> content, boolean preferLarge) {
if (!preferLarge) {
return new DenseStoreEntryComp(entry, showIcon, content);
return new DenseStoreEntryComp(entry, true, content);
} else {
return new StandardStoreEntryComp(entry, content);
}
@ -345,12 +348,32 @@ public abstract class StoreEntryComp extends SimpleComp {
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
move.getItems().add(m);
});
contextMenu.getItems().add(move);
}
{
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
var none = new MenuItem("None");
none.setOnAction(event -> {
wrapper.getEntry().setColor(null);
event.consume();
});
color.getItems().add(none);
Arrays.stream(DataStoreColor.values()).forEach(dataStoreColor -> {
MenuItem m = new MenuItem(DataStoreFormatter.capitalize(dataStoreColor.getId()));
m.setOnAction(event -> {
wrapper.getEntry().setColor(dataStoreColor);
event.consume();
});
color.getItems().add(m);
});
contextMenu.getItems().add(color);
}
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(wrapper.getDeletable().not());
del.setOnAction(event -> wrapper.delete());

View file

@ -7,6 +7,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
@ -37,6 +38,7 @@ public class StoreEntryWrapper {
private final BooleanProperty expanded = new SimpleBooleanProperty();
private final Property<Object> persistentState = new SimpleObjectProperty<>();
private final MapProperty<String, Object> cache = new SimpleMapProperty<>(FXCollections.observableHashMap());
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
public StoreEntryWrapper(DataStoreEntry entry) {
this.entry = entry;
@ -146,6 +148,7 @@ public class StoreEntryWrapper {
observing.setValue(entry.isObserving());
persistentState.setValue(entry.getStorePersistentState());
cache.putAll(entry.getStoreCache());
color.setValue(entry.getColor());
inRefresh.setValue(entry.isInRefresh());
deletable.setValue(entry.getConfiguration().isDeletable()

View file

@ -9,16 +9,20 @@ import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.binding.Bindings;
import javafx.css.PseudoClass;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
@ -80,6 +84,23 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
});
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
}).apply(struc -> SimpleChangeListener.apply(section.getWrapper().getColor(), val -> {
if (!topLevel) {
return;
}
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values())
.anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
struc.get().getStyleClass().remove("none");
if (val != null) {
struc.get().getStyleClass().add(val.getId());
} else {
struc.get().getStyleClass().add("none");
}
}))
.apply(struc -> {
struc.get().pseudoClassStateChanged(ROOT, topLevel);
struc.get().pseudoClassStateChanged(SUB, !topLevel);
})
.createStructure();
}

View file

@ -21,6 +21,7 @@ import javafx.scene.layout.Pane;
import javafx.stage.Window;
import javafx.util.Duration;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import java.nio.file.Files;
@ -40,6 +41,9 @@ public class AppTheme {
return;
}
Theme.ALL.forEach(theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));
stage.getScene().getRoot().getStyleClass().add(t.getCssId());
stage.getScene().getRoot().pseudoClassStateChanged(LIGHT, !t.isDark());
stage.getScene().getRoot().pseudoClassStateChanged(DARK, t.isDark());
SimpleChangeListener.apply(AppPrefs.get().performanceMode(),val -> {
@ -130,8 +134,8 @@ public class AppTheme {
private final String name;
public DerivedTheme(String id, String name, atlantafx.base.theme.Theme theme) {
super(id, theme);
public DerivedTheme(String id, String cssId, String name, atlantafx.base.theme.Theme theme) {
super(id, cssId, theme);
this.name = name;
}
@ -165,16 +169,16 @@ public class AppTheme {
@AllArgsConstructor
public static class Theme implements PrefsChoiceValue {
public static final Theme PRIMER_LIGHT = new Theme("light", new PrimerLight());
public static final Theme PRIMER_DARK = new Theme("dark", new PrimerDark());
public static final Theme NORD_LIGHT = new Theme("nordLight", new NordLight());
public static final Theme NORD_DARK = new Theme("nordDark", new NordDark());
public static final Theme CUPERTINO_LIGHT = new Theme("cupertinoLight", new CupertinoLight());
public static final Theme CUPERTINO_DARK = new Theme("cupertinoDark", new CupertinoDark());
public static final Theme DRACULA = new Theme("dracula", new Dracula());
public static final Theme PRIMER_LIGHT = new Theme("light", "primer", new PrimerLight());
public static final Theme PRIMER_DARK = new Theme("dark", "primer", new PrimerDark());
public static final Theme NORD_LIGHT = new Theme("nordLight", "nord", new NordLight());
public static final Theme NORD_DARK = new Theme("nordDark", "nord", new NordDark());
public static final Theme CUPERTINO_LIGHT = new Theme("cupertinoLight", "cupertino", new CupertinoLight());
public static final Theme CUPERTINO_DARK = new Theme("cupertinoDark", "cupertino", new CupertinoDark());
public static final Theme DRACULA = new Theme("dracula", "dracula", new Dracula());
// Adjust this to create your own theme
public static final Theme CUSTOM = new DerivedTheme("custom", "Custom", new PrimerDark());
public static final Theme CUSTOM = new DerivedTheme("custom", "primer", "Custom", new PrimerDark());
// Also include your custom theme here
public static final List<Theme> ALL = List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA);
@ -196,6 +200,8 @@ public class AppTheme {
}
protected final String id;
@Getter
protected final String cssId;
protected final atlantafx.base.theme.Theme theme;
public boolean isDark() {

View file

@ -0,0 +1,27 @@
package io.xpipe.app.storage;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@Getter
public enum DataStoreColor {
@JsonProperty("red")
RED("red", "\uD83D\uDFE5"),
@JsonProperty("green")
GREEN("green", "\uD83D\uDFE9"),
@JsonProperty("yellow")
YELLOW("yellow", "\uD83D\uDFE8"),
@JsonProperty("blue")
BLUE("blue", "\uD83D\uDFE6");
private final String id;
private final String emoji;
DataStoreColor(String id, String emoji) {
this.id = id;
this.emoji = emoji;
}
}

View file

@ -62,6 +62,10 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
JsonNode storePersistentStateNode;
@NonFinal
DataStoreColor color;
private DataStoreEntry(
Path directory,
UUID uuid,
@ -74,7 +78,8 @@ public class DataStoreEntry extends StorageElement {
Validity validity,
Configuration configuration,
JsonNode storePersistentState,
boolean expanded) {
boolean expanded, DataStoreColor color
) {
super(directory, uuid, name, lastUsed, lastModified, dirty);
this.categoryUuid = categoryUuid;
this.store = DataStorageParser.storeFromNode(storeNode);
@ -82,6 +87,7 @@ public class DataStoreEntry extends StorageElement {
this.validity = validity;
this.configuration = configuration;
this.expanded = expanded;
this.color = color;
this.provider = store != null
? DataStoreProviders.byStoreClass(store.getClass()).orElse(null)
: null;
@ -103,7 +109,8 @@ public class DataStoreEntry extends StorageElement {
store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE,
Configuration.defaultConfiguration(),
null,
false);
false, null
);
entry.refresh();
return entry;
}
@ -119,7 +126,8 @@ public class DataStoreEntry extends StorageElement {
JsonNode storeNode,
Configuration configuration,
JsonNode storePersistentState,
boolean expanded) {
boolean expanded,
DataStoreColor color) {
return new DataStoreEntry(
directory,
uuid,
@ -132,7 +140,8 @@ public class DataStoreEntry extends StorageElement {
Validity.INCOMPLETE,
configuration,
storePersistentState,
expanded);
expanded, color
);
}
public static DataStoreEntry fromDirectory(Path dir) throws Exception {
@ -178,6 +187,15 @@ public class DataStoreEntry extends StorageElement {
var expanded = Optional.ofNullable(stateJson.get("expanded"))
.map(jsonNode -> jsonNode.booleanValue())
.orElse(true);
var color = Optional.ofNullable(stateJson.get("color"))
.map(node -> {
try {
return mapper.treeToValue(node, DataStoreColor.class);
} catch (JsonProcessingException e) {
return null;
}
})
.orElse(null);
// Store loading is prone to errors.
JsonNode storeNode = null;
@ -196,7 +214,8 @@ public class DataStoreEntry extends StorageElement {
storeNode,
configuration,
persistentState,
expanded);
expanded,
color);
}
public void setInRefresh(boolean newRefresh) {
@ -274,6 +293,15 @@ public class DataStoreEntry extends StorageElement {
}
}
public void setColor(DataStoreColor newColor) {
var changed = !Objects.equals(color, newColor);
this.color = newColor;
if (changed) {
dirty = true;
notifyUpdate();
}
}
public boolean isDisabled() {
return validity == Validity.LOAD_FAILED;
}
@ -431,6 +459,7 @@ public class DataStoreEntry extends StorageElement {
obj.put("categoryUuid", categoryUuid.toString());
stateObj.put("lastUsed", lastUsed.toString());
stateObj.put("lastModified", lastModified.toString());
stateObj.set("color", mapper.valueToTree(color));
stateObj.set("persistentState", storePersistentStateNode);
obj.set("configuration", mapper.valueToTree(configuration));
stateObj.put("expanded", expanded);

View file

@ -5,6 +5,7 @@ lf=LF (Linux)
none=None
common=Common
key=Key
color=Color
passwordManager=Password manager
prompt=Prompt
customCommand=Custom command

View file

@ -13,6 +13,16 @@
-fx-spacing: 0.8em;
}
.root.nord .store-header-bar {
-fx-background-radius: 0;
-fx-border-radius: 0;
}
.root.nord .bar {
-fx-background-radius: 0;
-fx-border-radius: 0;
}
.bar .filter-bar .text-field {
-fx-padding: 0.35em;
-fx-text-fill: -color-fg-default;
@ -49,6 +59,11 @@
-fx-spacing: 0.2em;
}
.root.nord .store-creation-bar, .root.nord .store-sort-bar, .root.nord .store-category-bar {
-fx-background-radius: 0;
-fx-border-radius: 0;
}
.store-category-bar {
-fx-padding: 0.8em 0.5em 0.8em 0.5em;
-fx-background-color: -color-bg-subtle;

View file

@ -1,3 +1,12 @@
.store-list-comp .top {
-fx-border-width: 0 0 0.5em 0;
-fx-background-insets: 0 0 0.5em 0;
-fx-border-color: transparent;
-fx-background-color: transparent;
}
/* Grid */
.store-entry-grid .date, .store-entry-grid .summary {
-fx-text-fill: -color-fg-muted;
}
@ -21,7 +30,11 @@
.store-entry-grid .icon {
-fx-background-color: -color-bg-overlay;
-fx-background-radius: 5px;
-fx-background-radius: 5px;
}
.root.nord .store-entry-grid .icon {
-fx-background-radius: 3;
}
.root:pretty .store-entry-grid .icon {
@ -41,34 +54,16 @@
-fx-padding: 1px 6px 1px 6px;
}
.store-list-comp .top {
-fx-border-width: 0 0 0.7em 0;
-fx-background-insets: 0 0 0.7em 0;
-fx-border-color: transparent;
-fx-background-color: transparent;
}
.store-entry-section-comp:expanded:odd-depth {
-fx-background-color: -color-bg-default;
-fx-background-radius: 4px;
}
.store-entry-section-comp:expanded:even-depth {
-fx-background-radius: 4px;
}
.root:light .store-entry-section-comp:expanded:even-depth {
-fx-background-color: derive(-color-bg-default, -3%);
}
.root:dark .store-entry-section-comp:expanded:even-depth {
-fx-background-color: derive(-color-bg-default, 5%);
-fx-border-color: -color-border-subtle;
}
/* Entry */
.store-entry-comp {
-fx-border-color: transparent;
-fx-background-color: transparent;
-fx-background-radius: 4px;
}
.root.nord .store-entry-comp {
-fx-background-radius: 0;
}
.store-entry-comp:hover {
@ -99,6 +94,8 @@
-fx-padding: 6px;
}
/* Section */
.store-entry-section-comp .separator {
-fx-padding: 0 0.75em 0 0.75em;
-fx-border-insets: 0px;
@ -115,24 +112,146 @@
-fx-pref-height: 1;
}
.top > .store-entry-section-comp {
-fx-background-color: -color-bg-default;
-fx-background-radius: 4px;
}
.root:pretty .top > .store-entry-section-comp {
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 3px, 0.5, 0, 1);
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 1);
}
.store-entry-section-comp:expanded {
-fx-border-radius: 4px;
-fx-border-width: 1px;
-fx-border-color: -color-border-default;
.store-entry-section-comp {
-fx-border-radius: 4px;
-fx-background-radius: 4px;
}
.root.nord .store-entry-section-comp {
-fx-border-radius: 0;
-fx-background-radius: 0;
}
.store-entry-section-comp .list-box-view-comp .content {
-fx-spacing: 0.2em;
}
.store-entry-section-comp.none {
-fx-background-color: -color-bg-default;
-fx-border-color: -color-border-default;
}
/* Colors */
.root:light .top > .store-entry-section-comp.blue {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(130, 130, 250, 0.2) 40%, rgb(57, 57, 200, 0.2) 50%, rgb(137, 137, 250, 0.2) 100%);
-fx-border-color: rgba(80, 100, 150, 0.3);
}
.root:light .top > .store-entry-section-comp.blue > .separator .line {
-fx-border-color: rgba(80, 100, 150, 0.4);
}
.root:dark .top > .store-entry-section-comp.blue {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 30, 80, 0.8) 40%, rgb(27, 27, 65, 0.8) 50%, rgb(37, 37, 100, 0.8) 100%);
-fx-border-color: rgba(80, 100, 150, 0.7);
}
.root:dark .top > .store-entry-section-comp.blue > .separator .line {
-fx-border-color: rgba(80, 100, 150, 0.7);
}
.root:light .top > .store-entry-section-comp.red {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(220, 100, 100, 0.15) 40%, rgb(205, 50, 50, 0.15) 50%, rgb(200, 90, 90, 0.15) 100%);
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:light .top > .store-entry-section-comp.red > .separator .line {
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:dark .top > .store-entry-section-comp.red {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(80, 30, 30, 0.4) 40%, rgb(65, 27, 27, 0.4) 50%, rgb(100, 37, 37, 0.4) 100%);
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:dark .top > .store-entry-section-comp.red > .separator .line {
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:light .top > .store-entry-section-comp.yellow {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(180, 180, 30, 0.2) 40%, rgb(135, 135, 27, 0.2) 50%, rgb(200, 200, 37, 0.2) 100%);
-fx-border-color: rgba(170, 170, 80, 0.3);
}
.root:light .top > .store-entry-section-comp.yellow > .separator .line {
-fx-border-color: rgba(170, 170, 80, 0.5);
}
.root:dark .top > .store-entry-section-comp.yellow {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(80, 80, 30, 0.4) 40%, rgb(65, 65, 27, 0.4) 50%, rgb(100, 100, 37, 0.4) 100%);
-fx-border-color: rgba(150, 150, 80, 0.4);
}
.root:dark .top > .store-entry-section-comp.yellow > .separator .line {
-fx-border-color: rgba(170, 170, 80, 0.3);
}
.root:light .top > .store-entry-section-comp.green {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 180, 30, 0.1) 40%, rgb(20, 120, 20, 0.15) 50%, rgb(37, 200, 37, 0.1) 100%);
-fx-border-color: rgba(100, 150, 80, 0.2);
}
.root:light .top > .store-entry-section-comp.green > .separator .line {
-fx-border-color: rgba(100, 150, 80, 0.4);
}
.root:dark .top > .store-entry-section-comp.green {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 80, 30, 0.3) 40%, rgb(20, 60, 20, 0.3) 50%, rgb(37, 100, 37, 0.3) 100%);
-fx-border-color: rgba(100, 190, 80, 0.3);
}
.root:dark .top > .store-entry-section-comp.green > .separator .line {
-fx-border-color: rgba(100, 190, 80, 0.2);
}
/* Light sub backgrounds */
.root:light .store-entry-section-comp:sub:expanded {
-fx-border-color: #9999;
}
.root:light .store-entry-section-comp.none .store-entry-section-comp:expanded:even-depth {
-fx-background-color: derive(-color-bg-default, -3%);
}
.root:light .store-entry-section-comp.none .store-entry-section-comp:expanded:odd-depth {
-fx-background-color: -color-bg-default;
}
.root:light .store-entry-section-comp:sub:expanded:even-depth {
-fx-background-color: #ddd5;
}
.root:light .store-entry-section-comp:sub:expanded:odd-depth {
-fx-background-color: #aaa3;
}
/* Dark sub backgrounds */
.root:dark .store-entry-section-comp:sub:expanded {
-fx-border-color: #4449;
}
.root:dark .store-entry-section-comp.none .store-entry-section-comp:expanded:even-depth {
-fx-background-color: derive(-color-bg-default, 5%);
}
.root:dark .store-entry-section-comp.none .store-entry-section-comp:expanded:odd-depth {
-fx-background-color: -color-bg-default;
}
.root:dark .store-entry-section-comp:sub:expanded:even-depth {
-fx-background-color: #1114;
}
.root:dark .store-entry-section-comp:sub:expanded:odd-depth {
-fx-background-color: #2224;
}