clusters = new ArrayList<>(this.getClusters());
clusters.sort(Comparator.naturalOrder());
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
index cf57180..ec755c2 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Model.java
@@ -19,6 +19,10 @@ public class Model {
public void setActiveCluster(Cluster activeCluster) {
this.activeCluster = activeCluster;
+ this.notifyListeners();
+ }
+
+ public void notifyListeners() {
this.listeners.forEach(ModelListener::activeClusterUpdated);
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
index f57b616..da4d004 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
@@ -1,14 +1,11 @@
package nl.andrewlalis.crystalkeep.model;
-import nl.andrewlalis.crystalkeep.model.shards.ShardType;
-
import java.time.LocalDateTime;
import java.util.Objects;
/**
* A shard is a single "piece" of information, such as a snippet of text, login
- * credentials, an image, or a private key. All shards within a cluster should
- * have unique names.
+ * credentials, an image, or a private key.
*
* Due to the need to deserialize shards from byte arrays, it is required
* that this parent class holds a type discriminator value, which is used to
@@ -55,7 +52,7 @@ public abstract class Shard implements Comparable, CrystalItem {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Shard shard = (Shard) o;
- return getName().equals(shard.getName()) && getType() == shard.getType();
+ return getName().equals(shard.getName()) && getType() == shard.getType() && getCreatedAt().equals(shard.getCreatedAt());
}
@Override
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/ShardType.java b/src/main/java/nl/andrewlalis/crystalkeep/model/ShardType.java
similarity index 81%
rename from src/main/java/nl/andrewlalis/crystalkeep/model/shards/ShardType.java
rename to src/main/java/nl/andrewlalis/crystalkeep/model/ShardType.java
index 62a8827..bdfa277 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/ShardType.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/ShardType.java
@@ -1,6 +1,7 @@
-package nl.andrewlalis.crystalkeep.model.shards;
+package nl.andrewlalis.crystalkeep.model;
-import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+import nl.andrewlalis.crystalkeep.model.shards.TextShard;
/**
* Represents a distinct type of shard, and should correspond to exactly one
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
index ac451dd..1e5fe2e 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
@@ -3,7 +3,7 @@ package nl.andrewlalis.crystalkeep.model.serialization;
import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
-import nl.andrewlalis.crystalkeep.model.shards.ShardType;
+import nl.andrewlalis.crystalkeep.model.ShardType;
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
import java.io.IOException;
@@ -12,7 +12,6 @@ import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
index 984d5eb..55ea9f9 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
@@ -1,6 +1,7 @@
package nl.andrewlalis.crystalkeep.model.shards;
import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.ShardType;
import nl.andrewlalis.crystalkeep.model.serialization.ByteUtils;
import nl.andrewlalis.crystalkeep.model.serialization.ShardSerializer;
@@ -39,6 +40,11 @@ public class LoginCredentialsShard extends Shard {
return super.toString() + ", username=\"" + this.username + "\", password=\"" + this.password + "\"";
}
+ @Override
+ public String getIconPath() {
+ return "/nl/andrewlalis/crystalkeep/ui/images/login_credentials_shard_node_icon.png";
+ }
+
public static class Serializer implements ShardSerializer {
@Override
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
index 47bfdf9..ee6d75d 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
@@ -1,6 +1,7 @@
package nl.andrewlalis.crystalkeep.model.shards;
import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.ShardType;
import nl.andrewlalis.crystalkeep.model.serialization.ByteUtils;
import nl.andrewlalis.crystalkeep.model.serialization.ShardSerializer;
@@ -29,6 +30,11 @@ public class TextShard extends Shard {
return super.toString() + ", text=\"" + this.text + "\"";
}
+ @Override
+ public String getIconPath() {
+ return "/nl/andrewlalis/crystalkeep/ui/images/text_shard_node_icon.png";
+ }
+
public static class Serializer implements ShardSerializer {
@Override
public byte[] serialize(TextShard shard) throws IOException {
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
index 859ed74..e256e35 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
@@ -5,35 +5,54 @@ import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
+import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.CrystalItem;
+import nl.andrewlalis.crystalkeep.model.Model;
import nl.andrewlalis.crystalkeep.model.Shard;
import java.io.InputStream;
public class CrystalItemTreeCell extends TreeCell {
- private final ContextMenu contextMenu;
+ private final Model model;
- public CrystalItemTreeCell() {
- MenuItem item = new MenuItem("Delete");
- this.contextMenu = new ContextMenu(item);
+ public CrystalItemTreeCell(Model model) {
+ this.model = model;
}
+
@Override
protected void updateItem(CrystalItem item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
+ ContextMenu menu = new ContextMenu();
+ if (item instanceof Cluster) {
+ var addShardItem = new MenuItem("Add Shard");
+ var addClusterItem = new MenuItem("Add Cluster");
+ menu.getItems().addAll(addShardItem, addClusterItem);
+ }
+ var deleteItem = new MenuItem("Delete");
+ deleteItem.setOnAction(event -> {
+ if (this.getTreeItem().getParent() != null && this.getTreeItem().getParent() instanceof ClusterTreeItem) {
+ var cluster = ((ClusterTreeItem) this.getTreeItem().getParent()).getCluster();
+ if (item instanceof Shard) {
+ cluster.removeShard((Shard) item);
+ } else if (item instanceof Cluster) {
+ cluster.removeCluster((Cluster) item);
+ }
+ this.model.notifyListeners();
+ }
+ });
+ menu.getItems().add(deleteItem);
this.setText(item.getName());
InputStream is = getClass().getResourceAsStream(item.getIconPath());
if (is != null) {
ImageView icon = new ImageView(new Image(is));
- icon.setFitHeight(16);
+ icon.setFitHeight(24);
icon.setPreserveRatio(true);
this.setGraphic(icon);
}
- if (item instanceof Shard) {
- this.setContextMenu(this.contextMenu);
- }
+ this.setContextMenu(menu);
} else {
this.setText(null);
this.setGraphic(null);
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsPane.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsPane.java
new file mode 100644
index 0000000..e9f4b8a
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsPane.java
@@ -0,0 +1,56 @@
+package nl.andrewlalis.crystalkeep.view.shard_details;
+
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Pane;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+
+public class LoginCredentialsPane extends ShardPane {
+ public LoginCredentialsPane(LoginCredentialsShard shard) {
+ super(shard);
+ }
+
+ @Override
+ protected Node getContent(LoginCredentialsShard shard) {
+ GridPane gp = new GridPane();
+ gp.setPadding(new Insets(5));
+ gp.setHgap(5);
+ gp.setVgap(5);
+ gp.add(new Label("Username"), 0, 0);
+ var usernameField = new TextField(shard.getUsername());
+ usernameField.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setUsername(newValue);
+ });
+ gp.add(usernameField, 1, 0);
+ gp.add(new Label("Password"), 0, 1);
+ var passwordField = new PasswordField();
+ passwordField.setText(shard.getPassword());
+ var rawPasswordField = new TextField(shard.getPassword());
+ rawPasswordField.setVisible(false);
+ passwordField.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setPassword(newValue);
+ rawPasswordField.setText(newValue);
+ });
+ rawPasswordField.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setPassword(newValue);
+ passwordField.setText(newValue);
+ });
+ var passwordsContainer = new Pane();
+ passwordsContainer.getChildren().add(passwordField);
+ passwordsContainer.getChildren().add(rawPasswordField);
+ gp.add(passwordsContainer, 1, 1);
+ var showPasswordCheckbox = new CheckBox("Show password");
+ showPasswordCheckbox.setSelected(false);
+ showPasswordCheckbox.selectedProperty().addListener((observable, oldValue, newValue) -> {
+ passwordField.setVisible(!newValue);
+ rawPasswordField.setVisible(newValue);
+ });
+ gp.add(showPasswordCheckbox, 1, 2);
+ return gp;
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardPane.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardPane.java
new file mode 100644
index 0000000..93e4a8e
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardPane.java
@@ -0,0 +1,38 @@
+package nl.andrewlalis.crystalkeep.view.shard_details;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+import nl.andrewlalis.crystalkeep.model.Shard;
+
+import java.time.format.DateTimeFormatter;
+
+public abstract class ShardPane extends VBox {
+ public ShardPane(T shard) {
+ this.setSpacing(5);
+ GridPane gp = new GridPane();
+ gp.setPadding(new Insets(5));
+ gp.setHgap(5);
+ gp.setVgap(5);
+ gp.add(new Label("Name"), 0, 0);
+ var nameField = new TextField(shard.getName());
+ nameField.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setName(newValue);
+ });
+ gp.add(nameField, 1, 0);
+ gp.add(new Label("Created at"), 0, 1);
+ var createdAtField = new TextField(shard.getCreatedAt().format(DateTimeFormatter.ofPattern("dd MMMM yyyy HH:mm:ss")));
+ createdAtField.setEditable(false);
+ gp.add(createdAtField, 1, 1);
+ this.getChildren().add(gp);
+ this.getChildren().add(new Separator(Orientation.HORIZONTAL));
+ this.getChildren().add(this.getContent(shard));
+ }
+
+ protected abstract Node getContent(T shard);
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardPane.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardPane.java
new file mode 100644
index 0000000..c164dc1
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardPane.java
@@ -0,0 +1,20 @@
+package nl.andrewlalis.crystalkeep.view.shard_details;
+
+import javafx.scene.Node;
+import javafx.scene.control.TextArea;
+import nl.andrewlalis.crystalkeep.model.shards.TextShard;
+
+public class TextShardPane extends ShardPane {
+ public TextShardPane(TextShard shard) {
+ super(shard);
+ }
+
+ @Override
+ protected Node getContent(TextShard shard) {
+ var textArea = new TextArea(shard.getText());
+ textArea.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setText(newValue);
+ });
+ return textArea;
+ }
+}
diff --git a/src/main/resources/nl/andrewlalis/crystalkeep/ui/clusters_view.fxml b/src/main/resources/nl/andrewlalis/crystalkeep/ui/clusters_view.fxml
deleted file mode 100644
index 93bb4c6..0000000
--- a/src/main/resources/nl/andrewlalis/crystalkeep/ui/clusters_view.fxml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/login_credentials_shard_node_icon.png b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/login_credentials_shard_node_icon.png
new file mode 100644
index 0000000..ba169d3
Binary files /dev/null and b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/login_credentials_shard_node_icon.png differ
diff --git a/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/text_shard_node_icon.png b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/text_shard_node_icon.png
new file mode 100644
index 0000000..c623b85
Binary files /dev/null and b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/text_shard_node_icon.png differ