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 @@ + + + @@ -15,10 +18,17 @@ - + + + +
+ +
+
- +
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 @@ - - - - - -