diff --git a/designs/login_credentials_shard_node_icon.svg b/designs/login_credentials_shard_node_icon.svg
deleted file mode 100644
index 514f0c0..0000000
--- a/designs/login_credentials_shard_node_icon.svg
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
-
-
diff --git a/designs/shard_node_icon.svg b/designs/shard_node_icon.svg
index 24fc994..c5e7ae4 100644
--- a/designs/shard_node_icon.svg
+++ b/designs/shard_node_icon.svg
@@ -16,9 +16,9 @@
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="shard_node_icon.svg"
- inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\ui\images\shard_node_icon.png"
- inkscape:export-xdpi="192"
- inkscape:export-ydpi="192">
+ inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\shard_node_icon.png"
+ inkscape:export-xdpi="1536"
+ inkscape:export-ydpi="1536">
+ units="px"
+ showguides="true"
+ inkscape:guide-bbox="true" />
@@ -58,21 +60,9 @@
id="layer1"
transform="translate(0,-292.76667)">
+ style="fill:#00f8c5;stroke:none;stroke-width:0.26458333;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:1"
+ d="m 2.1143043,292.83045 1.1824699,2.0481 -1.1824699,2.0481 -1.18246994,-2.0481 1.18246994,-2.0481"
+ id="path820"
+ inkscape:connector-curvature="0" />
diff --git a/designs/shards/file.svg b/designs/shards/file.svg
new file mode 100644
index 0000000..2ee1ef1
--- /dev/null
+++ b/designs/shards/file.svg
@@ -0,0 +1,78 @@
+
+
+
+
diff --git a/designs/shards/login_credentials.svg b/designs/shards/login_credentials.svg
new file mode 100644
index 0000000..f2fcb90
--- /dev/null
+++ b/designs/shards/login_credentials.svg
@@ -0,0 +1,95 @@
+
+
+
+
diff --git a/designs/text_shard_node_icon.svg b/designs/shards/text.svg
similarity index 52%
rename from designs/text_shard_node_icon.svg
rename to designs/shards/text.svg
index 4a7b458..e9e50f1 100644
--- a/designs/text_shard_node_icon.svg
+++ b/designs/shards/text.svg
@@ -15,10 +15,10 @@
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
- sodipodi:docname="text_shard_node_icon.svg"
+ sodipodi:docname="text.svg"
inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\text_shard_node_icon.png"
- inkscape:export-xdpi="192"
- inkscape:export-ydpi="192">
+ inkscape:export-xdpi="1536"
+ inkscape:export-ydpi="1536">
+ units="px"
+ showguides="true"
+ inkscape:guide-bbox="true" />
@@ -57,33 +59,16 @@
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76667)">
-
A
+ id="tspan815"
+ x="0.36164278"
+ y="296.76907"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.69617891px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.35601118">T
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/AddShardHandler.java b/src/main/java/nl/andrewlalis/crystalkeep/control/AddShardHandler.java
index 528a24c..b15575b 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/AddShardHandler.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/control/AddShardHandler.java
@@ -53,7 +53,7 @@ public class AddShardHandler implements EventHandler {
switch (type) {
case TEXT: return new TextShard(name);
case LOGIN_CREDENTIALS: return new LoginCredentialsShard(name);
- case FILE: return new FileShard(name, "", "", new byte[0]);
+ case FILE: return new FileShard(name, "", new byte[0]);
default: return null;
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java b/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
index eee88e9..9c4f503 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
@@ -6,7 +6,7 @@ import javafx.scene.control.TreeItem;
import javafx.scene.layout.VBox;
import nl.andrewlalis.crystalkeep.model.CrystalItem;
import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
-import nl.andrewlalis.crystalkeep.view.shard_details.ViewModels;
+import nl.andrewlalis.crystalkeep.view.shards.ViewModels;
/**
* This listener will update the shard detail container pane (the main center
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java b/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
index bb1ae55..48c7eea 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/control/MainViewController.java
@@ -23,6 +23,8 @@ public class MainViewController implements ModelListener {
public TreeView clusterTreeView;
@FXML
public VBox shardDetailContainer;
+ @FXML
+ public Menu fileMenu;
public void init(Model model) {
this.model = model;
@@ -91,7 +93,6 @@ public class MainViewController implements ModelListener {
@FXML
public void save() {
if (model.getActiveCluster() == null) return;
- ClusterIO loader = new ClusterIO();
Path path = model.getActiveClusterPath();
if (path == null) {
FileChooser chooser = new FileChooser();
@@ -102,27 +103,12 @@ public class MainViewController implements ModelListener {
if (file == null) return;
path = file.toPath();
}
- char[] pw = this.model.getActiveClusterPassword();
- if (pw == null) {
- pw = this.promptPassword().orElse(new char[0]);
- }
- try {
- if (pw.length == 0) {
- loader.saveUnencrypted(model.getActiveCluster(), path);
- } else {
- loader.save(model.getActiveCluster(), path, pw);
- }
- } catch (Exception e) {
- e.printStackTrace();
- var alert = new Alert(Alert.AlertType.ERROR, "Could not save cluster.");
- alert.showAndWait();
- }
+ this.saveCluster(path);
}
@FXML
public void saveAs() {
if (model.getActiveCluster() == null) return;
- ClusterIO clusterIO = new ClusterIO();
Path path = model.getActiveClusterPath();
FileChooser chooser = new FileChooser();
chooser.setTitle("Save Cluster");
@@ -133,21 +119,7 @@ public class MainViewController implements ModelListener {
File file = chooser.showSaveDialog(this.clusterTreeView.getScene().getWindow());
if (file == null) return;
path = file.toPath();
- char[] pw = this.model.getActiveClusterPassword();
- if (pw == null) {
- pw = this.promptPassword().orElse(new char[0]);
- }
- try {
- if (pw.length == 0) {
- clusterIO.saveUnencrypted(model.getActiveCluster(), path);
- } else {
- clusterIO.save(model.getActiveCluster(), path, pw);
- }
- } catch (Exception e) {
- e.printStackTrace();
- var alert = new Alert(Alert.AlertType.ERROR, "Could not save cluster.");
- alert.showAndWait();
- }
+ this.saveCluster(path);
}
@FXML
@@ -175,4 +147,23 @@ public class MainViewController implements ModelListener {
});
return d.showAndWait();
}
+
+ private void saveCluster(Path path) {
+ char[] pw = this.model.getActiveClusterPassword();
+ if (pw == null) {
+ pw = this.promptPassword().orElse(new char[0]);
+ }
+ ClusterIO loader = new ClusterIO();
+ try {
+ if (pw.length == 0) {
+ loader.saveUnencrypted(model.getActiveCluster(), path);
+ } else {
+ loader.save(model.getActiveCluster(), path, pw);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ var alert = new Alert(Alert.AlertType.ERROR, "Could not save cluster.");
+ alert.showAndWait();
+ }
+ }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ByteUtils.java b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ByteUtils.java
index 5d4f0a6..97cfec5 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ByteUtils.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ByteUtils.java
@@ -1,6 +1,5 @@
package nl.andrewlalis.crystalkeep.io.serialization;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -25,6 +24,32 @@ public class ByteUtils {
os.write(toBytes(value));
}
+ public static byte[] longToBytes(long l) {
+ byte[] result = new byte[8];
+ for (int i = 7; i >= 0; i--) {
+ result[i] = (byte)(l & 0xFF);
+ l >>= 8;
+ }
+ return result;
+ }
+
+ public static long bytesToLong(final byte[] b) {
+ long result = 0;
+ for (int i = 0; i < 8; i++) {
+ result <<= 8;
+ result |= (b[i] & 0xFF);
+ }
+ return result;
+ }
+
+ public static void writeLong(long value, OutputStream os) throws IOException {
+ os.write(longToBytes(value));
+ }
+
+ public static long readLong(InputStream is) throws IOException {
+ return bytesToLong(is.readNBytes(Long.BYTES));
+ }
+
public static void writeLengthPrefixed(byte[] bytes, OutputStream os) throws IOException {
os.write(toBytes(bytes.length));
os.write(bytes);
@@ -34,12 +59,10 @@ public class ByteUtils {
writeLengthPrefixed(s.getBytes(StandardCharsets.UTF_8), os);
}
- public static byte[] writeLengthPrefixedStrings(String[] strings) throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ public static void writeLengthPrefixedStrings(String[] strings, OutputStream os) throws IOException {
for (String s : strings) {
- writeLengthPrefixed(s, bos);
+ writeLengthPrefixed(s, os);
}
- return bos.toByteArray();
}
public static byte[] readLengthPrefixed(InputStream is) throws IOException {
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ClusterSerializer.java b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ClusterSerializer.java
index 8603034..7745c6b 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ClusterSerializer.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ClusterSerializer.java
@@ -2,8 +2,9 @@ package nl.andrewlalis.crystalkeep.io.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.ShardType;
+import nl.andrewlalis.crystalkeep.model.shards.FileShard;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
import java.io.IOException;
@@ -27,6 +28,7 @@ public class ClusterSerializer {
static {
serializers.put(ShardType.TEXT, new TextShard.Serializer());
serializers.put(ShardType.LOGIN_CREDENTIALS, new LoginCredentialsShard.Serializer());
+ serializers.put(ShardType.FILE, new FileShard.Serializer());
}
/**
@@ -82,7 +84,7 @@ public class ClusterSerializer {
ByteUtils.writeLengthPrefixed(shard.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).getBytes(StandardCharsets.UTF_8), os);
os.write(ByteUtils.toBytes(shard.getType().getValue()));
ShardSerializer serializer = serializers.get(shard.getType());
- os.write(serializer.serialize(shard));
+ serializer.serialize(shard, os);
}
/**
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ShardSerializer.java b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ShardSerializer.java
index 1106aec..9dc2496 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ShardSerializer.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/io/serialization/ShardSerializer.java
@@ -4,10 +4,16 @@ import nl.andrewlalis.crystalkeep.model.Shard;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.time.LocalDateTime;
+/**
+ * This interface should be implemented to provide the ability to serialize and
+ * deserialize a shard to and from a stream of bytes.
+ * @param The shard type.
+ */
public interface ShardSerializer {
- byte[] serialize(T shard) throws IOException;
+ void serialize(T shard, OutputStream os) throws IOException;
T deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException;
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/FileShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/FileShard.java
index ebd8d56..ba15c5e 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/FileShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/FileShard.java
@@ -1,35 +1,57 @@
package nl.andrewlalis.crystalkeep.model.shards;
+import nl.andrewlalis.crystalkeep.io.serialization.ByteUtils;
+import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer;
import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.model.ShardType;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.time.LocalDateTime;
public class FileShard extends Shard {
private String fileName;
- private String mimeType;
private byte[] contents;
- public FileShard(String name, LocalDateTime createdAt, String fileName, String mimeType, byte[] contents) {
+ public FileShard(String name, LocalDateTime createdAt, String fileName, byte[] contents) {
super(name, createdAt, ShardType.FILE);
this.fileName = fileName;
- this.mimeType = mimeType;
this.contents = contents;
}
- public FileShard(String name, String fileName, String mimeType, byte[] contents) {
- this(name, LocalDateTime.now(), fileName, mimeType, contents);
+ public FileShard(String name, String fileName, byte[] contents) {
+ this(name, LocalDateTime.now(), fileName, contents);
}
public String getFileName() {
return fileName;
}
- public String getMimeType() {
- return mimeType;
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
}
public byte[] getContents() {
return contents;
}
+
+ public void setContents(byte[] contents) {
+ this.contents = contents;
+ }
+
+ public static final class Serializer implements ShardSerializer {
+ @Override
+ public void serialize(FileShard shard, OutputStream os) throws IOException {
+ ByteUtils.writeLengthPrefixed(shard.getFileName(), os);
+ ByteUtils.writeLengthPrefixed(shard.getContents(), os);
+ }
+
+ @Override
+ public FileShard deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException {
+ String fileName = ByteUtils.readLengthPrefixedString(is);
+ byte[] contents = ByteUtils.readLengthPrefixed(is);
+ return new FileShard(name, createdAt, fileName, contents);
+ }
+ }
}
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 7d7ba75..7e2103f 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
@@ -1,12 +1,13 @@
package nl.andrewlalis.crystalkeep.model.shards;
-import nl.andrewlalis.crystalkeep.model.Shard;
-import nl.andrewlalis.crystalkeep.model.ShardType;
import nl.andrewlalis.crystalkeep.io.serialization.ByteUtils;
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer;
+import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.ShardType;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.time.LocalDateTime;
public class LoginCredentialsShard extends Shard {
@@ -44,11 +45,11 @@ public class LoginCredentialsShard extends Shard {
return super.toString() + ", username=\"" + this.username + "\", password=\"" + this.password + "\"";
}
- public static class Serializer implements ShardSerializer {
+ public static final class Serializer implements ShardSerializer {
@Override
- public byte[] serialize(LoginCredentialsShard shard) throws IOException {
- return ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getUsername(), shard.getPassword()});
+ public void serialize(LoginCredentialsShard shard, OutputStream os) throws IOException {
+ ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getUsername(), shard.getPassword()}, os);
}
@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 328d132..44e5fed 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
@@ -1,12 +1,13 @@
package nl.andrewlalis.crystalkeep.model.shards;
-import nl.andrewlalis.crystalkeep.model.Shard;
-import nl.andrewlalis.crystalkeep.model.ShardType;
import nl.andrewlalis.crystalkeep.io.serialization.ByteUtils;
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer;
+import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.ShardType;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.time.LocalDateTime;
public class TextShard extends Shard {
@@ -34,10 +35,10 @@ public class TextShard extends Shard {
return super.toString() + ", text=\"" + this.text + "\"";
}
- public static class Serializer implements ShardSerializer {
+ public static final class Serializer implements ShardSerializer {
@Override
- public byte[] serialize(TextShard shard) throws IOException {
- return ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getText()});
+ public void serialize(TextShard shard, OutputStream os) throws IOException {
+ ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getText()}, os);
}
@Override
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/util/StringUtils.java b/src/main/java/nl/andrewlalis/crystalkeep/util/StringUtils.java
new file mode 100644
index 0000000..66d3d50
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/util/StringUtils.java
@@ -0,0 +1,10 @@
+package nl.andrewlalis.crystalkeep.util;
+
+public class StringUtils {
+ public static boolean endsWithAny(String s, String... suffixes) {
+ for (String suffix : suffixes) {
+ if (s.endsWith(suffix)) return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
index 8bafa6e..3b377a2 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
@@ -12,7 +12,7 @@ import nl.andrewlalis.crystalkeep.model.CrystalItem;
import nl.andrewlalis.crystalkeep.model.Model;
import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.util.ImageCache;
-import nl.andrewlalis.crystalkeep.view.shard_details.ViewModels;
+import nl.andrewlalis.crystalkeep.view.shards.ViewModels;
public class CrystalItemTreeCell extends TreeCell {
private static final String CLUSTER_ICON = "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png";
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shards/FileShardViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/FileShardViewModel.java
new file mode 100644
index 0000000..51066bf
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/FileShardViewModel.java
@@ -0,0 +1,92 @@
+package nl.andrewlalis.crystalkeep.view.shards;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+import javafx.stage.FileChooser;
+import nl.andrewlalis.crystalkeep.model.shards.FileShard;
+import nl.andrewlalis.crystalkeep.util.StringUtils;
+
+import java.io.*;
+
+public class FileShardViewModel extends ShardViewModel {
+ public FileShardViewModel(FileShard shard) {
+ super(shard);
+ }
+
+ @Override
+ protected Node getContent(FileShard shard) {
+ VBox container = new VBox(10);
+ container.setPadding(new Insets(5));
+ GridPane gp = new GridPane();
+ gp.setVgap(5);
+ gp.setHgap(5);
+
+ gp.add(new Label("File Name"), 0, 0);
+ TextField nameField = new TextField(shard.getFileName());
+ nameField.textProperty().addListener((observable, oldValue, newValue) -> {
+ shard.setFileName(newValue);
+ });
+ gp.add(nameField, 1, 0);
+ gp.add(new Label("File Size"), 0, 1);
+ TextField sizeField = new TextField(shard.getContents().length + " bytes");
+ sizeField.setEditable(false);
+ gp.add(sizeField, 1, 1);
+
+ Button setFileButton = new Button("Set File");
+ setFileButton.setOnAction(event -> {
+ FileChooser chooser = new FileChooser();
+ chooser.setTitle("Choose a File");
+ File file = chooser.showOpenDialog(gp.getScene().getWindow());
+ if (file != null) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ byte[] contents = fis.readAllBytes();
+ shard.setFileName(file.getName());
+ shard.setContents(contents);
+ nameField.setText(shard.getFileName());
+ sizeField.setText(shard.getContents().length + " bytes");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ gp.add(setFileButton, 0, 2);
+
+ Button extractFileButton = new Button("Extract File");
+ extractFileButton.setOnAction(event -> {
+ FileChooser chooser = new FileChooser();
+ chooser.setTitle("Extract File");
+ chooser.setInitialFileName(shard.getFileName());
+ File file = chooser.showSaveDialog(gp.getScene().getWindow());
+ if (file != null) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(shard.getContents());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ gp.add(extractFileButton, 1, 2);
+ container.getChildren().add(gp);
+
+ if (StringUtils.endsWithAny(shard.getFileName().toLowerCase(), ".png", ".jpg", ".jpeg", ".gif")) {
+ container.getChildren().add(new Separator(Orientation.HORIZONTAL));
+ ImageView imageView = new ImageView(new Image(new ByteArrayInputStream(shard.getContents())));
+ imageView.setPreserveRatio(true);
+ ScrollPane scrollPane = new ScrollPane(imageView);
+ container.getChildren().add(scrollPane);
+ }
+
+ return container;
+ }
+
+ @Override
+ public String getIconPath() {
+ return "/nl/andrewlalis/crystalkeep/ui/images/file_shard_node_icon.png";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/LoginCredentialsViewModel.java
similarity index 98%
rename from src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java
rename to src/main/java/nl/andrewlalis/crystalkeep/view/shards/LoginCredentialsViewModel.java
index b23cfb1..cae0a50 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/LoginCredentialsViewModel.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.crystalkeep.view.shard_details;
+package nl.andrewlalis.crystalkeep.view.shards;
import javafx.geometry.Insets;
import javafx.scene.Node;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/ShardViewModel.java
similarity index 96%
rename from src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java
rename to src/main/java/nl/andrewlalis/crystalkeep/view/shards/ShardViewModel.java
index 7c7e194..8826c0a 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/ShardViewModel.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.crystalkeep.view.shard_details;
+package nl.andrewlalis.crystalkeep.view.shards;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/TextShardViewModel.java
similarity index 92%
rename from src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardViewModel.java
rename to src/main/java/nl/andrewlalis/crystalkeep/view/shards/TextShardViewModel.java
index 97ffc9a..c134fd0 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/TextShardViewModel.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/TextShardViewModel.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.crystalkeep.view.shard_details;
+package nl.andrewlalis.crystalkeep.view.shards;
import javafx.scene.Node;
import javafx.scene.control.TextArea;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/ViewModels.java
similarity index 86%
rename from src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java
rename to src/main/java/nl/andrewlalis/crystalkeep/view/shards/ViewModels.java
index 09f4581..514f2a1 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shards/ViewModels.java
@@ -1,6 +1,7 @@
-package nl.andrewlalis.crystalkeep.view.shard_details;
+package nl.andrewlalis.crystalkeep.view.shards;
import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.shards.FileShard;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
@@ -17,6 +18,7 @@ public class ViewModels {
static {
shardViewModels.put(TextShard.class, TextShardViewModel.class);
shardViewModels.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class);
+ shardViewModels.put(FileShard.class, FileShardViewModel.class);
}
public static Optional> get(Shard shard) {
diff --git a/src/main/resources/nl/andrewlalis/crystalkeep/ui/crystalkeep.fxml b/src/main/resources/nl/andrewlalis/crystalkeep/ui/crystalkeep.fxml
index cbc72e8..18ffd1c 100644
--- a/src/main/resources/nl/andrewlalis/crystalkeep/ui/crystalkeep.fxml
+++ b/src/main/resources/nl/andrewlalis/crystalkeep/ui/crystalkeep.fxml
@@ -4,7 +4,7 @@
-