diff --git a/drongo b/drongo index ed056bc4..d740895b 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit ed056bc49fb919799f70a6d7ee2dd65a38c25b5d +Subproject commit d740895bd68f0ded97e382e5944feb26fa759875 diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index dd3468c9..54272bc9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -37,8 +37,8 @@ public class MainApp extends Application { appController.initializeView(); Wallet wallet = new Wallet(); - wallet.setPolicyType(PolicyType.SINGLE); - wallet.setScriptType(ScriptType.P2PKH); + wallet.setPolicyType(PolicyType.MULTI); + wallet.setScriptType(ScriptType.P2SH); KeystoreImportDialog dlg = new KeystoreImportDialog(wallet); dlg.showAndWait(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java index 29171ed8..be4edfdf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java @@ -51,7 +51,7 @@ public class DevicePane extends TitledPane { this.wallet = wallet; this.device = device; - setPadding(new Insets(0, 0, 0, 0)); + setPadding(Insets.EMPTY); setGraphic(getTitle()); getStyleClass().add("devicepane"); @@ -92,14 +92,14 @@ public class DevicePane extends TitledPane { labelsBox.setAlignment(Pos.CENTER_LEFT); this.mainLabel = new Label(); mainLabel.setText(device.getModel().toDisplayString()); - mainLabel.getStyleClass().add("devicelist-main-label"); + mainLabel.getStyleClass().add("main-label"); labelsBox.getChildren().add(mainLabel); this.statusLabel = new Label(); statusLabel.textProperty().bind(status); labelsBox.getChildren().add(statusLabel); - statusLabel.getStyleClass().add("devicelist-status-label"); + statusLabel.getStyleClass().add("status-label"); listItem.getChildren().add(labelsBox); HBox.setHgrow(labelsBox, Priority.ALWAYS); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreFileImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreFileImportPane.java new file mode 100644 index 00000000..1c2a87d3 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreFileImportPane.java @@ -0,0 +1,153 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.KeystoreImportEvent; +import com.sparrowwallet.sparrow.external.KeystoreFileImport; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TitledPane; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import org.controlsfx.control.HyperlinkLabel; + +import java.io.*; + +public class KeystoreFileImportPane extends TitledPane { + private final KeystoreImportAccordion importAccordion; + private final Wallet wallet; + private final KeystoreFileImport importer; + + private Label mainLabel; + private HyperlinkLabel descriptionLabel; + + public KeystoreFileImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreFileImport importer) { + this.importAccordion = importAccordion; + this.wallet = wallet; + this.importer = importer; + + setPadding(Insets.EMPTY); + + setGraphic(getTitle()); + getStyleClass().add("importpane"); + setContent(getContentBox(importer.getKeystoreImportDescription())); + + Platform.runLater(() -> { + Node arrow = this.lookup(".arrow"); + if(arrow != null) { + arrow.setVisible(false); + arrow.setManaged(false); + } + }); + } + + private Node getTitle() { + HBox listItem = new HBox(); + listItem.setPadding(new Insets(10, 20, 10, 10)); + listItem.setSpacing(10); + + HBox imageBox = new HBox(); + imageBox.setMinWidth(50); + imageBox.setMinHeight(50); + listItem.getChildren().add(imageBox); + + Image image = new Image("image/" + importer.getWalletModel().getType() + ".png", 50, 50, true, true); + if (!image.isError()) { + ImageView imageView = new ImageView(); + imageView.setImage(image); + imageBox.getChildren().add(imageView); + } + + VBox labelsBox = new VBox(); + labelsBox.setSpacing(5); + labelsBox.setAlignment(Pos.CENTER_LEFT); + this.mainLabel = new Label(); + mainLabel.setText(importer.getName()); + mainLabel.getStyleClass().add("main-label"); + labelsBox.getChildren().add(mainLabel); + + this.descriptionLabel = new HyperlinkLabel(); + + labelsBox.getChildren().add(descriptionLabel); + descriptionLabel.getStyleClass().add("description-label"); + descriptionLabel.setText("Keystore file import [View Details...]"); + descriptionLabel.setOnAction(event -> { + setExpanded(true); + }); + listItem.getChildren().add(labelsBox); + HBox.setHgrow(labelsBox, Priority.ALWAYS); + + HBox buttonBox = new HBox(); + buttonBox.setAlignment(Pos.CENTER_RIGHT); + + Button importButton = new Button("Import File..."); + importButton.setAlignment(Pos.CENTER_RIGHT); + importButton.setOnAction(event -> { + importFile(); + }); + + buttonBox.getChildren().add(importButton); + listItem.getChildren().add(buttonBox); + + this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> { + listItem.setPrefWidth(newValue.getWidth()); + }); + + return listItem; + } + + private void importFile() { + Stage window = new Stage(); + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("All Files", "*.*"), + new FileChooser.ExtensionFilter("JSON", "*.json") + ); + + File file = fileChooser.showOpenDialog(window); + if(file != null) { + importFile(file); + } + } + + private void importFile(File file) { + if(file.exists()) { + try { + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream); + EventManager.get().post(new KeystoreImportEvent(keystore)); + } catch (Exception e) { + setExpanded(false); + descriptionLabel.getStyleClass().remove("description-label"); + descriptionLabel.getStyleClass().add("description-error"); + descriptionLabel.setText("Error Importing [View Details...]"); + setContent(getContentBox(e.getMessage())); + } + } + } + + private Node getContentBox(String message) { + Label details = new Label(message); + details.setWrapText(true); + + HBox contentBox = new HBox(); + contentBox.setAlignment(Pos.TOP_RIGHT); + contentBox.getChildren().add(details); + contentBox.setPadding(new Insets(10, 30, 10, 30)); + contentBox.setPrefHeight(60); + + return contentBox; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java new file mode 100644 index 00000000..60dd2f57 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java @@ -0,0 +1,32 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.external.KeystoreFileImport; +import com.sparrowwallet.sparrow.external.KeystoreImport; +import com.sparrowwallet.sparrow.external.KeystoreMnemonicImport; +import javafx.collections.ObservableList; +import javafx.scene.control.Accordion; + +import java.util.List; + +public class KeystoreImportAccordion extends Accordion { + private List importers; + + public void setKeystoreImporters(Wallet wallet, ObservableList importers) { + this.importers = importers; + + for(KeystoreImport importer : importers) { + KeystoreFileImportPane importPane = null; + + if(importer instanceof KeystoreFileImport) { + importPane = new KeystoreFileImportPane(this, wallet, (KeystoreFileImport)importer); + } else if(importer instanceof KeystoreMnemonicImport) { + //TODO: + } else { + throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass()); + } + + this.getPanes().add(importPane); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java index 247dcffe..868f9ab2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java @@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletModel; import com.sparrowwallet.sparrow.storage.Storage; import java.io.*; @@ -17,19 +18,24 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -public class ColdcardMultisig implements MultisigWalletImport, KeystoreImport, WalletExport { +public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImport, WalletExport { private final Gson gson = new Gson(); @Override public String getName() { - return "Coldcard (Multisig)"; + return "Coldcard Multisig"; } @Override - public PolicyType getPolicyType() { + public PolicyType getKeystorePolicyType() { return PolicyType.MULTI; } + @Override + public WalletModel getWalletModel() { + return WalletModel.COLDCARD; + } + @Override public Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException { InputStreamReader reader = new InputStreamReader(inputStream); diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java index a90fea4e..fbd3f688 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java @@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletModel; import java.io.InputStream; import java.io.InputStreamReader; @@ -16,7 +17,7 @@ import java.util.List; import static com.sparrowwallet.drongo.protocol.ScriptType.*; -public class ColdcardSinglesig implements SinglesigWalletImport { +public class ColdcardSinglesig implements KeystoreFileImport, SinglesigWalletImport { public static final List ALLOWED_SCRIPT_TYPES = List.of(P2PKH, P2SH_P2WPKH, P2WPKH); @Override @@ -25,7 +26,29 @@ public class ColdcardSinglesig implements SinglesigWalletImport { } @Override - public Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException { + public PolicyType getKeystorePolicyType() { + return PolicyType.SINGLE; + } + + @Override + public String getKeystoreImportDescription() { + return "Import file created by using the Advanced > Dump Summary feature on your Coldcard"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.COLDCARD; + } + + @Override + public Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException { + Wallet wallet = importWallet(scriptType, inputStream); + + return wallet.getKeystores().get(0); + } + + @Override + public Wallet importWallet(ScriptType scriptType, InputStream inputStream) throws ImportException { if(!ALLOWED_SCRIPT_TYPES.contains(scriptType)) { throw new ImportException("Script type of " + scriptType + " is not allowed"); } diff --git a/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java index 0abd2c33..6c4ff1c9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java @@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletModel; import java.io.*; import java.lang.reflect.Type; @@ -17,12 +18,43 @@ import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; -public class Electrum implements SinglesigWalletImport, MultisigWalletImport, WalletExport { +public class Electrum implements KeystoreFileImport, SinglesigWalletImport, MultisigWalletImport, WalletExport { @Override public String getName() { return "Electrum"; } + @Override + public PolicyType getKeystorePolicyType() { + return PolicyType.SINGLE; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.ELECTRUM; + } + + @Override + public String getKeystoreImportDescription() { + return "Import from an Electrum wallet"; + } + + @Override + public Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException { + Wallet wallet = importWallet(inputStream); + + if(!wallet.getPolicyType().equals(PolicyType.SINGLE) || wallet.getKeystores().size() != 1) { + throw new ImportException("Multisig wallet detected - import it through the File > Import menu"); + } + + if(!wallet.getScriptType().equals(scriptType)) { + //TODO: Derive appropriate ScriptType keystore + throw new ImportException("Wallet has an incompatible script type of " + wallet.getScriptType()); + } + + return wallet.getKeystores().get(0); + } + @Override public Wallet importWallet(InputStream inputStream) throws ImportException { InputStreamReader reader = new InputStreamReader(inputStream); @@ -89,7 +121,7 @@ public class Electrum implements SinglesigWalletImport, MultisigWalletImport, Wa } @Override - public Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException { + public Wallet importWallet(ScriptType scriptType, InputStream inputStream) throws ImportException { Wallet wallet = importWallet(inputStream); wallet.setScriptType(scriptType); diff --git a/src/main/java/com/sparrowwallet/sparrow/external/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/external/Hwi.java index 457b4f15..13d1e4d6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/Hwi.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/Hwi.java @@ -23,29 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Set; -public class Hwi implements KeystoreImport { +public class Hwi { private static File hwiExecutable; - @Override - public String getName() { - return "Hardware Wallet"; - } - - @Override - public String getKeystoreImportDescription() { - return "Imports a connected hardware wallet"; - } - - @Override - public PolicyType getPolicyType() { - return PolicyType.SINGLE; - } - - @Override - public Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException { - return null; - } - public List enumerate(String passphrase) throws ImportException { try { List command; diff --git a/src/main/java/com/sparrowwallet/sparrow/external/KeystoreFileImport.java b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreFileImport.java new file mode 100644 index 00000000..f046958c --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreFileImport.java @@ -0,0 +1,10 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; + +import java.io.InputStream; + +public interface KeystoreFileImport extends KeystoreImport { + Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException; +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java index fe27e113..d47e50d5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java @@ -1,13 +1,10 @@ package com.sparrowwallet.sparrow.external; import com.sparrowwallet.drongo.policy.PolicyType; -import com.sparrowwallet.drongo.protocol.ScriptType; -import com.sparrowwallet.drongo.wallet.Keystore; - -import java.io.InputStream; +import com.sparrowwallet.drongo.wallet.WalletModel; public interface KeystoreImport extends Import { - PolicyType getPolicyType(); - Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException; + PolicyType getKeystorePolicyType(); + WalletModel getWalletModel(); String getKeystoreImportDescription(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/external/KeystoreMnemonicImport.java b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreMnemonicImport.java new file mode 100644 index 00000000..38481f36 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreMnemonicImport.java @@ -0,0 +1,8 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; + +public interface KeystoreMnemonicImport extends KeystoreImport { + Keystore getKeystore(ScriptType scriptType, String[] mnemonicWords, String passphrase) throws ImportException; +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java b/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java index 5aeadf55..8a2beb7c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java @@ -7,5 +7,5 @@ import java.io.InputStream; public interface SinglesigWalletImport extends Import { String getWalletImportDescription(); - Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException; + Wallet importWallet(ScriptType scriptType, InputStream inputStream) throws ImportException; } diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportController.java index 4d417de6..4914a0f5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportController.java +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportController.java @@ -63,6 +63,7 @@ public class KeystoreImportController implements Initializable { Node importTypeNode = importLoader.load(); KeystoreImportDetailController controller = importLoader.getController(); controller.setMasterController(this); + controller.initializeView(); importPane.getChildren().add(importTypeNode); return importLoader; diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDetailController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDetailController.java index 000a2de5..effa1460 100644 --- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDetailController.java +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDetailController.java @@ -10,4 +10,8 @@ public abstract class KeystoreImportDetailController { void setMasterController(KeystoreImportController masterController) { this.masterController = masterController; } + + public void initializeView() { + + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDialog.java index 8d5862ed..debc5c0a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/KeystoreImportDialog.java @@ -43,6 +43,7 @@ public class KeystoreImportDialog extends Dialog { @Subscribe public void keystoreImported(KeystoreImportEvent event) { this.keystore = event.getKeystore(); + System.out.println(keystore.getLabel() + " " + keystore.getKeyDerivation().getMasterFingerprint()); this.close(); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/UsbAirgappedController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/UsbAirgappedController.java new file mode 100644 index 00000000..1a2ed110 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/UsbAirgappedController.java @@ -0,0 +1,28 @@ +package com.sparrowwallet.sparrow.keystoreimport; + +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.sparrow.control.KeystoreImportAccordion; +import com.sparrowwallet.sparrow.external.ColdcardMultisig; +import com.sparrowwallet.sparrow.external.ColdcardSinglesig; +import com.sparrowwallet.sparrow.external.KeystoreImport; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; + +import java.util.Collections; +import java.util.List; + +public class UsbAirgappedController extends KeystoreImportDetailController { + @FXML + private KeystoreImportAccordion importAccordion; + + public void initializeView() { + List importers = Collections.emptyList(); + if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE)) { + importers = List.of(new ColdcardSinglesig()); + } else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI)) { + importers = List.of(new ColdcardMultisig()); + } + + importAccordion.setKeystoreImporters(getMasterController().getWallet(), FXCollections.observableList(importers)); + } +} diff --git a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/hw_airgapped.fxml b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/hw_airgapped.fxml new file mode 100644 index 00000000..77e73fb2 --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/hw_airgapped.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css index 7d06fdb8..17747c29 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css +++ b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css @@ -47,14 +47,19 @@ -fx-translate-x: -1000; } -.devicelist-main-label .text { +.main-label .text { } -.devicelist-status-label .text { +.status-label .text, .description-label Text { -fx-fill: #a0a1a7; } -.status-error .text { +.status-error .text, .description-error Text { -fx-fill: #ca1243; -} \ No newline at end of file +} + +.description-label .text, description-error .text { + -fx-fill: #1e88cf; +} +