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");
|
URL url = CrystalKeep.class.getClassLoader().getResource("ui/crystalkeep.fxml");
|
||||||
FXMLLoader loader = new FXMLLoader(url);
|
FXMLLoader loader = new FXMLLoader(url);
|
||||||
Model model = new Model();
|
Model model = new Model();
|
||||||
loader.setController(new MainViewController(model));
|
|
||||||
var scene = new Scene(loader.load());
|
var scene = new Scene(loader.load());
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setTitle("CrystalKeep");
|
stage.setTitle("CrystalKeep");
|
||||||
|
@ -43,14 +42,20 @@ public class CrystalKeep extends Application {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
rootCluster = new Cluster("Root");
|
rootCluster = new Cluster("Root");
|
||||||
rootCluster.addShard(new TextShard(rootCluster, "Example Shard", LocalDateTime.now(), "Hello world!"));
|
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);
|
clusterLoader.saveDefault(rootCluster);
|
||||||
System.out.println("Saved root cluster on first load.");
|
System.out.println("Saved root cluster on first load.");
|
||||||
}
|
}
|
||||||
|
|
||||||
model.setActiveCluster(rootCluster);
|
model.setActiveCluster(rootCluster);
|
||||||
System.out.println(rootCluster);
|
|
||||||
|
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
|
MainViewController controller = loader.getController();
|
||||||
|
controller.init(model);
|
||||||
|
System.out.println(rootCluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void test() throws IOException {
|
public static void test() throws IOException {
|
||||||
|
|
|
@ -2,16 +2,48 @@ package nl.andrewlalis.crystalkeep.control;
|
||||||
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
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.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;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class MainViewController {
|
public class MainViewController implements ModelListener {
|
||||||
private final Model model;
|
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 = 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
|
@FXML
|
||||||
|
@ -23,4 +55,23 @@ public class MainViewController {
|
||||||
e.printStackTrace();
|
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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class Cluster implements Comparable<Cluster> {
|
public class Cluster implements Comparable<Cluster>, CrystalItem {
|
||||||
@Setter
|
@Setter
|
||||||
private String name;
|
private String name;
|
||||||
private final Set<Shard> shards;
|
private final Set<Shard> shards;
|
||||||
|
@ -37,6 +35,18 @@ public class Cluster implements Comparable<Cluster> {
|
||||||
cluster.setParent(this);
|
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
|
@Override
|
||||||
public int compareTo(Cluster o) {
|
public int compareTo(Cluster o) {
|
||||||
return this.getName().compareTo(o.getName());
|
return this.getName().compareTo(o.getName());
|
||||||
|
@ -68,4 +78,9 @@ public class Cluster implements Comparable<Cluster> {
|
||||||
}
|
}
|
||||||
return sb.toString();
|
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 lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class Model {
|
public class Model {
|
||||||
private Cluster activeCluster;
|
private Cluster activeCluster;
|
||||||
|
|
||||||
|
private final Set<ModelListener> listeners = new HashSet<>();
|
||||||
|
|
||||||
|
public void addListener(ModelListener listener) {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void setActiveCluster(Cluster activeCluster) {
|
public void setActiveCluster(Cluster activeCluster) {
|
||||||
this.activeCluster = 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>
|
* </p>
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class Shard implements Comparable<Shard> {
|
public abstract class Shard implements Comparable<Shard>, CrystalItem {
|
||||||
@Setter
|
@Setter
|
||||||
private Cluster cluster;
|
private Cluster cluster;
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -57,4 +57,9 @@ public abstract class Shard implements Comparable<Shard> {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Shard: name=\"" + this.name + "\", type=" + this.type + ", createdAt=" + this.createdAt;
|
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.control.MenuItem?>
|
||||||
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?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">
|
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewlalis.crystalkeep.control.MainViewController">
|
||||||
<MenuBar>
|
<MenuBar>
|
||||||
<Menu text="File">
|
<Menu text="File">
|
||||||
|
@ -15,10 +18,17 @@
|
||||||
</MenuBar>
|
</MenuBar>
|
||||||
<BorderPane>
|
<BorderPane>
|
||||||
<left>
|
<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>
|
</left>
|
||||||
<center>
|
<center>
|
||||||
<fx:include source="shard_detail.fxml" />
|
<VBox fx:id="shardDetailContainer" minWidth="600" />
|
||||||
</center>
|
</center>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</VBox>
|
</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