Added ability to render the clusters in a tree and have per-item context menus.
This commit is contained in:
parent
2e78b95588
commit
4d40431bcd
|
@ -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 {
|
||||
|
|
|
@ -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<CrystalItem> 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<CrystalItem> root = this.createNode(this.model.getActiveCluster());
|
||||
this.clusterTreeView.setRoot(root);
|
||||
this.clusterTreeView.setShowRoot(true);
|
||||
}
|
||||
|
||||
private TreeItem<CrystalItem> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
public class ShardDetailController {
|
||||
}
|
|
@ -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<Cluster> {
|
||||
public class Cluster implements Comparable<Cluster>, CrystalItem {
|
||||
@Setter
|
||||
private String name;
|
||||
private final Set<Shard> shards;
|
||||
|
@ -37,6 +35,18 @@ public class Cluster implements Comparable<Cluster> {
|
|||
cluster.setParent(this);
|
||||
}
|
||||
|
||||
public List<Cluster> getClustersOrdered() {
|
||||
List<Cluster> clusters = new ArrayList<>(this.getClusters());
|
||||
clusters.sort(Comparator.naturalOrder());
|
||||
return clusters;
|
||||
}
|
||||
|
||||
public List<Shard> getShardsOrdered() {
|
||||
List<Shard> 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<Cluster> {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconPath() {
|
||||
return "ui/images/cluster_node_icon.png";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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<ModelListener> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewlalis.crystalkeep.model;
|
||||
|
||||
public interface ModelListener {
|
||||
void activeClusterUpdated();
|
||||
}
|
|
@ -18,7 +18,7 @@ import java.util.Objects;
|
|||
* </p>
|
||||
*/
|
||||
@Getter
|
||||
public abstract class Shard implements Comparable<Shard> {
|
||||
public abstract class Shard implements Comparable<Shard>, CrystalItem {
|
||||
@Setter
|
||||
private Cluster cluster;
|
||||
@Setter
|
||||
|
@ -57,4 +57,9 @@ public abstract class Shard implements Comparable<Shard> {
|
|||
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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CrystalItem> {
|
||||
@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);
|
||||
}
|
||||
}
|
|
@ -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<CrystalItem> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CrystalItem> {
|
||||
@Getter
|
||||
private final Shard shard;
|
||||
|
||||
public ShardTreeItem(Shard shard) {
|
||||
super(shard);
|
||||
this.shard = shard;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
<?import javafx.scene.control.MenuItem?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TreeView?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewlalis.crystalkeep.control.MainViewController">
|
||||
<MenuBar>
|
||||
<Menu text="File">
|
||||
|
@ -15,10 +18,17 @@
|
|||
</MenuBar>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<fx:include source="clusters_view.fxml" />
|
||||
<BorderPane prefWidth="200.0">
|
||||
<top>
|
||||
<Label text="Clusters" BorderPane.alignment="TOP_CENTER"/>
|
||||
</top>
|
||||
<center>
|
||||
<TreeView fx:id="clusterTreeView" prefWidth="200.0"/>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</left>
|
||||
<center>
|
||||
<fx:include source="shard_detail.fxml" />
|
||||
<VBox fx:id="shardDetailContainer" minWidth="600" />
|
||||
</center>
|
||||
</BorderPane>
|
||||
</VBox>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="nl.andrewlalis.crystalkeep.control.ShardDetailController"
|
||||
prefHeight="400.0" prefWidth="600.0">
|
||||
|
||||
</AnchorPane>
|
Loading…
Reference in New Issue