Added File shard, improved shard icons.

This commit is contained in:
Andrew Lalis 2021-06-03 09:42:23 +02:00
parent a4bc985472
commit a9c335dcca
26 changed files with 417 additions and 226 deletions

View File

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333333 4.2333333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="login_credentials_shard_node_icon.svg"
inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\login_credentials_shard_node_icon.png"
inkscape:export-xdpi="192"
inkscape:export-ydpi="192">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="89.600001"
inkscape:cx="10.229603"
inkscape:cy="7.4126105"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76667)">
<path
sodipodi:type="star"
style="fill:#72deff;fill-opacity:1;stroke:none;stroke-width:0.07803907;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="path817"
sodipodi:sides="9"
sodipodi:cx="2.1166666"
sodipodi:cy="294.93815"
sodipodi:r1="1.8178079"
sodipodi:r2="1.2179312"
sodipodi:arg1="0.52359878"
sodipodi:arg2="0.87266463"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 3.6909344,295.84704 -0.7913968,0.0241 -0.1611441,0.77519 -0.6217269,-0.49025 -0.621727,0.49025 -0.1611441,-0.77519 -0.79139678,-0.0241 0.37483979,-0.69741 -0.59076328,-0.52715 0.73543197,-0.2933 -0.11370504,-0.78356 0.75190744,0.24804 0.416557,-0.67333 0.4165569,0.67333 0.7519075,-0.24804 -0.1137051,0.78356 0.735432,0.2933 -0.5907633,0.52715 z"
inkscape:transform-center-x="0.06028767"
inkscape:transform-center-y="0.030212626" />
<g
id="g828">
<rect
y="294.87439"
x="1.5420023"
height="0.95215333"
width="1.1493286"
id="rect816"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.05291667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke" />
<path
inkscape:connector-curvature="0"
id="path818"
d="m 1.8074821,294.89943 v -0.43431 c 0,0 -0.016704,-0.32365 0.3236467,-0.32365 0.340351,0 0.292326,0.29233 0.292326,0.29233 v 0.47816"
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="295.05811"
x="1.7157624"
height="0.17957026"
width="0.8018086"
id="rect820"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.05291667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.05291667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="rect822"
width="0.8018086"
height="0.17957026"
x="1.7157624"
y="295.41309" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -16,9 +16,9 @@
id="svg8" id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)" inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="shard_node_icon.svg" 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-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\shard_node_icon.png"
inkscape:export-xdpi="192" inkscape:export-xdpi="1536"
inkscape:export-ydpi="192"> inkscape:export-ydpi="1536">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview <sodipodi:namedview
@ -28,9 +28,9 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="31.678384" inkscape:zoom="44.8"
inkscape:cx="4.3963569" inkscape:cx="8.5805581"
inkscape:cy="7.5940447" inkscape:cy="7.3490264"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="false" showgrid="false"
@ -39,7 +39,9 @@
inkscape:window-x="-8" inkscape:window-x="-8"
inkscape:window-y="-8" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
units="px" /> units="px"
showguides="true"
inkscape:guide-bbox="true" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -58,21 +60,9 @@
id="layer1" id="layer1"
transform="translate(0,-292.76667)"> transform="translate(0,-292.76667)">
<path <path
sodipodi:type="star" 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"
style="fill:#72deff;fill-opacity:1;stroke:none;stroke-width:0.07803907;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke" d="m 2.1143043,292.83045 1.1824699,2.0481 -1.1824699,2.0481 -1.18246994,-2.0481 1.18246994,-2.0481"
id="path817" id="path820"
sodipodi:sides="9" inkscape:connector-curvature="0" />
sodipodi:cx="2.1166666"
sodipodi:cy="294.93815"
sodipodi:r1="1.8178079"
sodipodi:r2="1.2179312"
sodipodi:arg1="0.52359878"
sodipodi:arg2="0.87266463"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 3.6909344,295.84704 -0.7913968,0.0241 -0.1611441,0.77519 -0.6217269,-0.49025 -0.621727,0.49025 -0.1611441,-0.77519 -0.79139678,-0.0241 0.37483979,-0.69741 -0.59076328,-0.52715 0.73543197,-0.2933 -0.11370504,-0.78356 0.75190744,0.24804 0.416557,-0.67333 0.4165569,0.67333 0.7519075,-0.24804 -0.1137051,0.78356 0.735432,0.2933 -0.5907633,0.52715 z"
inkscape:transform-center-x="0.06028767"
inkscape:transform-center-y="0.030212626" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

78
designs/shards/file.svg Normal file
View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333333 4.2333333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="file.svg"
inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\file_shard_node_icon.png"
inkscape:export-xdpi="1536"
inkscape:export-ydpi="1536">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="10.333173"
inkscape:cy="9.4475152"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76667)">
<g
id="g832"
transform="matrix(2.2606665,0,0,2.2606665,-2.7169913,-371.69624)">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path826"
d="m 2.4538923,294.00575 v 0.19782 H 2.664343 Z"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.01322917;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" />
<path
inkscape:connector-curvature="0"
id="path824"
d="m 1.6119693,295.71377 v -1.70802 h 0.841923 v 0.19782 H 2.664343 v 1.5102 z"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.02645833;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333333 4.2333333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="login_credentials.svg"
inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\login_credentials_shard_node_icon.png"
inkscape:export-xdpi="1536"
inkscape:export-ydpi="1536">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="45.254834"
inkscape:cx="12.226157"
inkscape:cy="8.1661844"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:lockguides="true">
<sodipodi:guide
position="0.52618688,3.2974378"
orientation="1,0"
id="guide815"
inkscape:locked="true" />
<sodipodi:guide
position="1.0348342,3.7066942"
orientation="0,1"
id="guide817"
inkscape:locked="true" />
<sodipodi:guide
position="3.7008477,2.8238696"
orientation="1,0"
id="guide819"
inkscape:locked="true" />
<sodipodi:guide
position="-0.59634513,0.5320334"
orientation="0,1"
id="guide821"
inkscape:locked="true" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76667)">
<path
style="fill:#edf9fd;stroke:#000000;stroke-width:0.396875;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:1"
d="m 0.52618687,294.18783 v -0.89452 H 3.7008477 v 3.17466 H 0.52618687 v -1.07576"
id="path823"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.396875;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 0.22324218,294.76758 H 2.5879557 l -0.72347,-0.72347 0.72347,0.72347 -0.7420736,0.74207"
id="path825"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -15,10 +15,10 @@
version="1.1" version="1.1"
id="svg8" id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 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-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-xdpi="1536"
inkscape:export-ydpi="192"> inkscape:export-ydpi="1536">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview <sodipodi:namedview
@ -29,8 +29,8 @@
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="44.8" inkscape:zoom="44.8"
inkscape:cx="8.2633984" inkscape:cx="5.8060872"
inkscape:cy="6.8552682" inkscape:cy="8.9176258"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="false" showgrid="false"
@ -39,7 +39,9 @@
inkscape:window-x="-8" inkscape:window-x="-8"
inkscape:window-y="-8" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
units="px" /> units="px"
showguides="true"
inkscape:guide-bbox="true" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -57,33 +59,16 @@
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="layer1" id="layer1"
transform="translate(0,-292.76667)"> transform="translate(0,-292.76667)">
<path
sodipodi:type="star"
style="fill:#72deff;fill-opacity:1;stroke:none;stroke-width:0.07803907;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="path817"
sodipodi:sides="9"
sodipodi:cx="2.1166666"
sodipodi:cy="294.93815"
sodipodi:r1="1.8178079"
sodipodi:r2="1.2179312"
sodipodi:arg1="0.52359878"
sodipodi:arg2="0.87266463"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 3.6909344,295.84704 -0.7913968,0.0241 -0.1611441,0.77519 -0.6217269,-0.49025 -0.621727,0.49025 -0.1611441,-0.77519 -0.79139678,-0.0241 0.37483979,-0.69741 -0.59076328,-0.52715 0.73543197,-0.2933 -0.11370504,-0.78356 0.75190744,0.24804 0.416557,-0.67333 0.4165569,0.67333 0.7519075,-0.24804 -0.1137051,0.78356 0.735432,0.2933 -0.5907633,0.52715 z"
inkscape:transform-center-x="0.06028767"
inkscape:transform-center-y="0.030212626" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:1.9992249px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.12495156" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.69617891px;line-height:1.25;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;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35601118"
x="1.4333378" x="0.36164278"
y="295.61206" y="296.76907"
id="text818"><tspan id="text817"><tspan
sodipodi:role="line" sodipodi:role="line"
id="tspan816" id="tspan815"
x="1.4333378" x="0.36164278"
y="295.61206" y="296.76907"
style="stroke-width:0.12495156">A</tspan></text> 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</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -53,7 +53,7 @@ public class AddShardHandler implements EventHandler<ActionEvent> {
switch (type) { switch (type) {
case TEXT: return new TextShard(name); case TEXT: return new TextShard(name);
case LOGIN_CREDENTIALS: return new LoginCredentialsShard(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; default: return null;
} }
} }

View File

@ -6,7 +6,7 @@ import javafx.scene.control.TreeItem;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import nl.andrewlalis.crystalkeep.model.CrystalItem; import nl.andrewlalis.crystalkeep.model.CrystalItem;
import nl.andrewlalis.crystalkeep.view.ShardTreeItem; 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 * This listener will update the shard detail container pane (the main center

View File

@ -23,6 +23,8 @@ public class MainViewController implements ModelListener {
public TreeView<CrystalItem> clusterTreeView; public TreeView<CrystalItem> clusterTreeView;
@FXML @FXML
public VBox shardDetailContainer; public VBox shardDetailContainer;
@FXML
public Menu fileMenu;
public void init(Model model) { public void init(Model model) {
this.model = model; this.model = model;
@ -91,7 +93,6 @@ public class MainViewController implements ModelListener {
@FXML @FXML
public void save() { public void save() {
if (model.getActiveCluster() == null) return; if (model.getActiveCluster() == null) return;
ClusterIO loader = new ClusterIO();
Path path = model.getActiveClusterPath(); Path path = model.getActiveClusterPath();
if (path == null) { if (path == null) {
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
@ -102,27 +103,12 @@ public class MainViewController implements ModelListener {
if (file == null) return; if (file == null) return;
path = file.toPath(); path = file.toPath();
} }
char[] pw = this.model.getActiveClusterPassword(); this.saveCluster(path);
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();
}
} }
@FXML @FXML
public void saveAs() { public void saveAs() {
if (model.getActiveCluster() == null) return; if (model.getActiveCluster() == null) return;
ClusterIO clusterIO = new ClusterIO();
Path path = model.getActiveClusterPath(); Path path = model.getActiveClusterPath();
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
chooser.setTitle("Save Cluster"); chooser.setTitle("Save Cluster");
@ -133,21 +119,7 @@ public class MainViewController implements ModelListener {
File file = chooser.showSaveDialog(this.clusterTreeView.getScene().getWindow()); File file = chooser.showSaveDialog(this.clusterTreeView.getScene().getWindow());
if (file == null) return; if (file == null) return;
path = file.toPath(); path = file.toPath();
char[] pw = this.model.getActiveClusterPassword(); this.saveCluster(path);
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();
}
} }
@FXML @FXML
@ -175,4 +147,23 @@ public class MainViewController implements ModelListener {
}); });
return d.showAndWait(); 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();
}
}
} }

View File

@ -1,6 +1,5 @@
package nl.andrewlalis.crystalkeep.io.serialization; package nl.andrewlalis.crystalkeep.io.serialization;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -25,6 +24,32 @@ public class ByteUtils {
os.write(toBytes(value)); 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 { public static void writeLengthPrefixed(byte[] bytes, OutputStream os) throws IOException {
os.write(toBytes(bytes.length)); os.write(toBytes(bytes.length));
os.write(bytes); os.write(bytes);
@ -34,12 +59,10 @@ public class ByteUtils {
writeLengthPrefixed(s.getBytes(StandardCharsets.UTF_8), os); writeLengthPrefixed(s.getBytes(StandardCharsets.UTF_8), os);
} }
public static byte[] writeLengthPrefixedStrings(String[] strings) throws IOException { public static void writeLengthPrefixedStrings(String[] strings, OutputStream os) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (String s : strings) { for (String s : strings) {
writeLengthPrefixed(s, bos); writeLengthPrefixed(s, os);
} }
return bos.toByteArray();
} }
public static byte[] readLengthPrefixed(InputStream is) throws IOException { public static byte[] readLengthPrefixed(InputStream is) throws IOException {

View File

@ -2,8 +2,9 @@ package nl.andrewlalis.crystalkeep.io.serialization;
import nl.andrewlalis.crystalkeep.model.Cluster; import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.Shard; import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.ShardType; 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 nl.andrewlalis.crystalkeep.model.shards.TextShard;
import java.io.IOException; import java.io.IOException;
@ -27,6 +28,7 @@ public class ClusterSerializer {
static { static {
serializers.put(ShardType.TEXT, new TextShard.Serializer()); serializers.put(ShardType.TEXT, new TextShard.Serializer());
serializers.put(ShardType.LOGIN_CREDENTIALS, new LoginCredentialsShard.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); ByteUtils.writeLengthPrefixed(shard.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).getBytes(StandardCharsets.UTF_8), os);
os.write(ByteUtils.toBytes(shard.getType().getValue())); os.write(ByteUtils.toBytes(shard.getType().getValue()));
ShardSerializer serializer = serializers.get(shard.getType()); ShardSerializer serializer = serializers.get(shard.getType());
os.write(serializer.serialize(shard)); serializer.serialize(shard, os);
} }
/** /**

View File

@ -4,10 +4,16 @@ import nl.andrewlalis.crystalkeep.model.Shard;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime; 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 <T> The shard type.
*/
public interface ShardSerializer<T extends Shard> { public interface ShardSerializer<T extends Shard> {
byte[] serialize(T shard) throws IOException; void serialize(T shard, OutputStream os) throws IOException;
T deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException; T deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException;
} }

View File

@ -1,35 +1,57 @@
package nl.andrewlalis.crystalkeep.model.shards; 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.Shard;
import nl.andrewlalis.crystalkeep.model.ShardType; import nl.andrewlalis.crystalkeep.model.ShardType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class FileShard extends Shard { public class FileShard extends Shard {
private String fileName; private String fileName;
private String mimeType;
private byte[] contents; 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); super(name, createdAt, ShardType.FILE);
this.fileName = fileName; this.fileName = fileName;
this.mimeType = mimeType;
this.contents = contents; this.contents = contents;
} }
public FileShard(String name, String fileName, String mimeType, byte[] contents) { public FileShard(String name, String fileName, byte[] contents) {
this(name, LocalDateTime.now(), fileName, mimeType, contents); this(name, LocalDateTime.now(), fileName, contents);
} }
public String getFileName() { public String getFileName() {
return fileName; return fileName;
} }
public String getMimeType() { public void setFileName(String fileName) {
return mimeType; this.fileName = fileName;
} }
public byte[] getContents() { public byte[] getContents() {
return contents; return contents;
} }
public void setContents(byte[] contents) {
this.contents = contents;
}
public static final class Serializer implements ShardSerializer<FileShard> {
@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);
}
}
} }

View File

@ -1,12 +1,13 @@
package nl.andrewlalis.crystalkeep.model.shards; 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.ByteUtils;
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class LoginCredentialsShard extends Shard { public class LoginCredentialsShard extends Shard {
@ -44,11 +45,11 @@ public class LoginCredentialsShard extends Shard {
return super.toString() + ", username=\"" + this.username + "\", password=\"" + this.password + "\""; return super.toString() + ", username=\"" + this.username + "\", password=\"" + this.password + "\"";
} }
public static class Serializer implements ShardSerializer<LoginCredentialsShard> { public static final class Serializer implements ShardSerializer<LoginCredentialsShard> {
@Override @Override
public byte[] serialize(LoginCredentialsShard shard) throws IOException { public void serialize(LoginCredentialsShard shard, OutputStream os) throws IOException {
return ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getUsername(), shard.getPassword()}); ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getUsername(), shard.getPassword()}, os);
} }
@Override @Override

View File

@ -1,12 +1,13 @@
package nl.andrewlalis.crystalkeep.model.shards; 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.ByteUtils;
import nl.andrewlalis.crystalkeep.io.serialization.ShardSerializer; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class TextShard extends Shard { public class TextShard extends Shard {
@ -34,10 +35,10 @@ public class TextShard extends Shard {
return super.toString() + ", text=\"" + this.text + "\""; return super.toString() + ", text=\"" + this.text + "\"";
} }
public static class Serializer implements ShardSerializer<TextShard> { public static final class Serializer implements ShardSerializer<TextShard> {
@Override @Override
public byte[] serialize(TextShard shard) throws IOException { public void serialize(TextShard shard, OutputStream os) throws IOException {
return ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getText()}); ByteUtils.writeLengthPrefixedStrings(new String[]{shard.getText()}, os);
} }
@Override @Override

View File

@ -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;
}
}

View File

@ -12,7 +12,7 @@ import nl.andrewlalis.crystalkeep.model.CrystalItem;
import nl.andrewlalis.crystalkeep.model.Model; import nl.andrewlalis.crystalkeep.model.Model;
import nl.andrewlalis.crystalkeep.model.Shard; import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.util.ImageCache; 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<CrystalItem> { public class CrystalItemTreeCell extends TreeCell<CrystalItem> {
private static final String CLUSTER_ICON = "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png"; private static final String CLUSTER_ICON = "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png";

View File

@ -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<FileShard> {
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";
}
}

View File

@ -1,4 +1,4 @@
package nl.andrewlalis.crystalkeep.view.shard_details; package nl.andrewlalis.crystalkeep.view.shards;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;

View File

@ -1,4 +1,4 @@
package nl.andrewlalis.crystalkeep.view.shard_details; package nl.andrewlalis.crystalkeep.view.shards;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;

View File

@ -1,4 +1,4 @@
package nl.andrewlalis.crystalkeep.view.shard_details; package nl.andrewlalis.crystalkeep.view.shards;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;

View File

@ -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.Shard;
import nl.andrewlalis.crystalkeep.model.shards.FileShard;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard; import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.shards.TextShard; import nl.andrewlalis.crystalkeep.model.shards.TextShard;
@ -17,6 +18,7 @@ public class ViewModels {
static { static {
shardViewModels.put(TextShard.class, TextShardViewModel.class); shardViewModels.put(TextShard.class, TextShardViewModel.class);
shardViewModels.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class); shardViewModels.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class);
shardViewModels.put(FileShard.class, FileShardViewModel.class);
} }
public static Optional<ShardViewModel<?>> get(Shard shard) { public static Optional<ShardViewModel<?>> get(Shard shard) {

View File

@ -4,7 +4,7 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<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 fx:id="fileMenu" text="File">
<MenuItem text="New" onAction="#newCluster" /> <MenuItem text="New" onAction="#newCluster" />
<MenuItem text="Load" onAction="#load" /> <MenuItem text="Load" onAction="#load" />
<MenuItem text="Save" onAction="#save" /> <MenuItem text="Save" onAction="#save" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 795 B

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB