From 4d40431bcd552b8c966085e8998f3e7058495537 Mon Sep 17 00:00:00 2001
From: Andrew Lalis
Date: Sat, 29 May 2021 10:43:35 +0200
Subject: [PATCH] Added ability to render the clusters in a tree and have
per-item context menus.
---
.../andrewlalis/crystalkeep/CrystalKeep.java | 11 +++-
.../control/MainViewController.java | 59 +++++++++++++++++--
.../control/ShardDetailController.java | 4 --
.../crystalkeep/model/Cluster.java | 23 ++++++--
.../crystalkeep/model/CrystalItem.java | 10 ++++
.../andrewlalis/crystalkeep/model/Model.java | 10 ++++
.../crystalkeep/model/ModelListener.java | 5 ++
.../andrewlalis/crystalkeep/model/Shard.java | 7 ++-
.../crystalkeep/view/ClusterTreeItem.java | 21 +++++++
.../crystalkeep/view/CrystalItemTreeCell.java | 37 ++++++++++++
.../crystalkeep/view/ShardTreeItem.java | 16 +++++
src/main/resources/ui/crystalkeep.fxml | 14 ++++-
src/main/resources/ui/shard_detail.fxml | 9 ---
13 files changed, 199 insertions(+), 27 deletions(-)
delete mode 100644 src/main/java/nl/andrewlalis/crystalkeep/control/ShardDetailController.java
create mode 100644 src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
create mode 100644 src/main/java/nl/andrewlalis/crystalkeep/model/ModelListener.java
create mode 100644 src/main/java/nl/andrewlalis/crystalkeep/view/ClusterTreeItem.java
create mode 100644 src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
create mode 100644 src/main/java/nl/andrewlalis/crystalkeep/view/ShardTreeItem.java
delete mode 100644 src/main/resources/ui/shard_detail.fxml
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java b/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
index 7431a08..045a046 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
@@ -29,7 +29,6 @@ public class CrystalKeep extends Application {
URL url = CrystalKeep.class.getClassLoader().getResource("ui/crystalkeep.fxml");
FXMLLoader loader = new FXMLLoader(url);
Model model = new Model();
- loader.setController(new MainViewController(model));
var scene = new Scene(loader.load());
stage.setScene(scene);
stage.setTitle("CrystalKeep");
@@ -43,14 +42,20 @@ public class CrystalKeep extends Application {
} catch (IOException e) {
rootCluster = new Cluster("Root");
rootCluster.addShard(new TextShard(rootCluster, "Example Shard", LocalDateTime.now(), "Hello world!"));
+ rootCluster.addShard(new LoginCredentialsShard(rootCluster, "Netflix", LocalDateTime.now(), "user", "secret password"));
+ for (int i = 0; i < 100; i++) {
+ rootCluster.addShard(new TextShard(rootCluster, "test " + i, LocalDateTime.now(), "value: " + i));
+ }
clusterLoader.saveDefault(rootCluster);
System.out.println("Saved root cluster on first load.");
}
-
model.setActiveCluster(rootCluster);
- System.out.println(rootCluster);
stage.show();
+
+ MainViewController controller = loader.getController();
+ controller.init(model);
+ System.out.println(rootCluster);
}
public static void test() throws IOException {
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java b/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
index 75de4d2..b3aca19 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
@@ -2,16 +2,48 @@ package nl.andrewlalis.crystalkeep.control;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
-import nl.andrewlalis.crystalkeep.model.Model;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TreeItem;
+import javafx.scene.control.TreeView;
+import javafx.scene.layout.VBox;
+import nl.andrewlalis.crystalkeep.model.*;
import nl.andrewlalis.crystalkeep.model.serialization.ClusterLoader;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+import nl.andrewlalis.crystalkeep.model.shards.TextShard;
+import nl.andrewlalis.crystalkeep.view.ClusterTreeItem;
+import nl.andrewlalis.crystalkeep.view.CrystalItemTreeCell;
+import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
import java.io.IOException;
-public class MainViewController {
- private final Model model;
+public class MainViewController implements ModelListener {
+ private Model model;
- public MainViewController(Model model) {
+ @FXML
+ public TreeView clusterTreeView;
+ @FXML
+ public VBox shardDetailContainer;
+
+ public void init(Model model) {
this.model = model;
+ this.model.addListener(this);
+ this.activeClusterUpdated();
+ assert(this.clusterTreeView != null);
+ this.clusterTreeView.setCellFactory(param -> new CrystalItemTreeCell());
+ this.clusterTreeView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ shardDetailContainer.getChildren().clear();
+ if (newValue instanceof ShardTreeItem) {
+ var node = (ShardTreeItem) newValue;
+ System.out.println(node.getShard());
+ if (node.getShard() instanceof TextShard) {
+ shardDetailContainer.getChildren().add(new TextArea(((TextShard) node.getShard()).getText()));
+ } else if (node.getShard() instanceof LoginCredentialsShard) {
+ shardDetailContainer.getChildren().add(new Label("Username: " + ((LoginCredentialsShard) node.getShard()).getUsername()));
+ shardDetailContainer.getChildren().add(new Label("Password: " + ((LoginCredentialsShard) node.getShard()).getPassword()));
+ }
+ }
+ });
}
@FXML
@@ -23,4 +55,23 @@ public class MainViewController {
e.printStackTrace();
}
}
+
+ @Override
+ public void activeClusterUpdated() {
+ if (this.model.getActiveCluster() == null) return;
+ TreeItem root = this.createNode(this.model.getActiveCluster());
+ this.clusterTreeView.setRoot(root);
+ this.clusterTreeView.setShowRoot(true);
+ }
+
+ private TreeItem createNode(Cluster cluster) {
+ ClusterTreeItem node = new ClusterTreeItem(cluster);
+ for (Cluster child : cluster.getClustersOrdered()) {
+ node.getChildren().add(this.createNode(child));
+ }
+ for (Shard shard : cluster.getShardsOrdered()) {
+ node.getChildren().add(new ShardTreeItem(shard));
+ }
+ return node;
+ }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/ShardDetailController.java b/src/main/java/nl/andrewlalis/crystalkeep/control/ShardDetailController.java
deleted file mode 100644
index 06f9cab..0000000
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/ShardDetailController.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package nl.andrewlalis.crystalkeep.control;
-
-public class ShardDetailController {
-}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
index d51248a..cd9ed4e 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
@@ -3,12 +3,10 @@ package nl.andrewlalis.crystalkeep.model;
import lombok.Getter;
import lombok.Setter;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
@Getter
-public class Cluster implements Comparable {
+public class Cluster implements Comparable, CrystalItem {
@Setter
private String name;
private final Set shards;
@@ -37,6 +35,18 @@ public class Cluster implements Comparable {
cluster.setParent(this);
}
+ public List getClustersOrdered() {
+ List clusters = new ArrayList<>(this.getClusters());
+ clusters.sort(Comparator.naturalOrder());
+ return clusters;
+ }
+
+ public List getShardsOrdered() {
+ List shards = new ArrayList<>(this.getShards());
+ shards.sort(Comparator.naturalOrder());
+ return shards;
+ }
+
@Override
public int compareTo(Cluster o) {
return this.getName().compareTo(o.getName());
@@ -68,4 +78,9 @@ public class Cluster implements Comparable {
}
return sb.toString();
}
+
+ @Override
+ public String getIconPath() {
+ return "ui/images/cluster_node_icon.png";
+ }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java b/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
new file mode 100644
index 0000000..3fcaaaa
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
@@ -0,0 +1,10 @@
+package nl.andrewlalis.crystalkeep.model;
+
+/**
+ * Unifying interface for both clusters and shards and any other possible
+ * components in the hierarchy.
+ */
+public interface CrystalItem {
+ String getName();
+ String getIconPath();
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
index 29a009a..3f02fc1 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
@@ -2,11 +2,21 @@ package nl.andrewlalis.crystalkeep.model;
import lombok.Getter;
+import java.util.HashSet;
+import java.util.Set;
+
@Getter
public class Model {
private Cluster activeCluster;
+ private final Set listeners = new HashSet<>();
+
+ public void addListener(ModelListener listener) {
+ this.listeners.add(listener);
+ }
+
public void setActiveCluster(Cluster activeCluster) {
this.activeCluster = activeCluster;
+ this.listeners.forEach(ModelListener::activeClusterUpdated);
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/ModelListener.java b/src/main/java/nl/andrewlalis/crystalkeep/model/ModelListener.java
new file mode 100644
index 0000000..4e45790
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/ModelListener.java
@@ -0,0 +1,5 @@
+package nl.andrewlalis.crystalkeep.model;
+
+public interface ModelListener {
+ void activeClusterUpdated();
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
index 475b358..c5de7de 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
@@ -18,7 +18,7 @@ import java.util.Objects;
*
*/
@Getter
-public abstract class Shard implements Comparable {
+public abstract class Shard implements Comparable, CrystalItem {
@Setter
private Cluster cluster;
@Setter
@@ -57,4 +57,9 @@ public abstract class Shard implements Comparable {
public String toString() {
return "Shard: name=\"" + this.name + "\", type=" + this.type + ", createdAt=" + this.createdAt;
}
+
+ @Override
+ public String getIconPath() {
+ return "ui/images/shard_node_icon.png";
+ }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/ClusterTreeItem.java b/src/main/java/nl/andrewlalis/crystalkeep/view/ClusterTreeItem.java
new file mode 100644
index 0000000..9245c6f
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/ClusterTreeItem.java
@@ -0,0 +1,21 @@
+package nl.andrewlalis.crystalkeep.view;
+
+import javafx.scene.control.TreeItem;
+import javafx.scene.image.ImageView;
+import lombok.Getter;
+import nl.andrewlalis.crystalkeep.model.Cluster;
+import nl.andrewlalis.crystalkeep.model.CrystalItem;
+
+public class ClusterTreeItem extends TreeItem {
+ @Getter
+ private final Cluster cluster;
+
+ public ClusterTreeItem(Cluster cluster) {
+ super(cluster);
+ this.cluster = cluster;
+ ImageView clusterIcon = new ImageView("ui/images/cluster_node_icon.png");
+ clusterIcon.setFitHeight(16);
+ clusterIcon.setPreserveRatio(true);
+ super.setGraphic(clusterIcon);
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
new file mode 100644
index 0000000..c18bbcf
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
@@ -0,0 +1,37 @@
+package nl.andrewlalis.crystalkeep.view;
+
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.TreeCell;
+import javafx.scene.image.ImageView;
+import nl.andrewlalis.crystalkeep.model.CrystalItem;
+import nl.andrewlalis.crystalkeep.model.Shard;
+
+public class CrystalItemTreeCell extends TreeCell {
+ private final ContextMenu contextMenu;
+
+ public CrystalItemTreeCell() {
+ MenuItem item = new MenuItem("Delete");
+ this.contextMenu = new ContextMenu(item);
+ }
+
+ @Override
+ protected void updateItem(CrystalItem item, boolean empty) {
+ super.updateItem(item, empty);
+
+ if (!empty) {
+ this.setText(item.getName());
+ ImageView icon = new ImageView(item.getIconPath());
+ icon.setFitHeight(16);
+ icon.setPreserveRatio(true);
+ this.setGraphic(icon);
+ if (item instanceof Shard) {
+ this.setContextMenu(this.contextMenu);
+ }
+ } else {
+ this.setText(null);
+ this.setGraphic(null);
+ this.setContextMenu(null);
+ }
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/ShardTreeItem.java b/src/main/java/nl/andrewlalis/crystalkeep/view/ShardTreeItem.java
new file mode 100644
index 0000000..9ec06a5
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/ShardTreeItem.java
@@ -0,0 +1,16 @@
+package nl.andrewlalis.crystalkeep.view;
+
+import javafx.scene.control.TreeItem;
+import lombok.Getter;
+import nl.andrewlalis.crystalkeep.model.CrystalItem;
+import nl.andrewlalis.crystalkeep.model.Shard;
+
+public class ShardTreeItem extends TreeItem {
+ @Getter
+ private final Shard shard;
+
+ public ShardTreeItem(Shard shard) {
+ super(shard);
+ this.shard = shard;
+ }
+}
diff --git a/src/main/resources/ui/crystalkeep.fxml b/src/main/resources/ui/crystalkeep.fxml
index 8cdbdea..5855dda 100644
--- a/src/main/resources/ui/crystalkeep.fxml
+++ b/src/main/resources/ui/crystalkeep.fxml
@@ -7,6 +7,9 @@
+
+
+
-
+
+
+
+
+
+
+
+
-
+
diff --git a/src/main/resources/ui/shard_detail.fxml b/src/main/resources/ui/shard_detail.fxml
deleted file mode 100644
index 787ff62..0000000
--- a/src/main/resources/ui/shard_detail.fxml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-