瀏覽代碼

Rework choose-location-screen in add vault wizard: (#1620)

* more checks for the chosen vault path
* every check has own error message
* perform checks when vault path changes
* if any radio button selected, enable vault path field (no-edit)
Armin Schrenk 4 年之前
父節點
當前提交
d6e4c7d177

+ 59 - 45
main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java

@@ -1,10 +1,10 @@
 package org.cryptomator.ui.addvaultwizard;
 
 import dagger.Lazy;
-import org.cryptomator.ui.common.ErrorComponent;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.controls.FontAwesome5IconView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -15,21 +15,21 @@ import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.beans.value.ObservableValue;
 import javafx.fxml.FXML;
+import javafx.scene.Node;
 import javafx.scene.Scene;
+import javafx.scene.control.Label;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.Toggle;
 import javafx.scene.control.ToggleGroup;
 import javafx.stage.DirectoryChooser;
 import javafx.stage.Stage;
 import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ResourceBundle;
@@ -43,55 +43,72 @@ public class CreateNewVaultLocationController implements FxController {
 	private final Stage window;
 	private final Lazy<Scene> chooseNameScene;
 	private final Lazy<Scene> choosePasswordScene;
-	private final ErrorComponent.Builder errorComponent;
 	private final LocationPresets locationPresets;
 	private final ObjectProperty<Path> vaultPath;
 	private final StringProperty vaultName;
 	private final ResourceBundle resourceBundle;
 	private final BooleanBinding validVaultPath;
 	private final BooleanProperty usePresetPath;
-	private final StringProperty warningText;
+	private final StringProperty statusText;
+	private final ObjectProperty<Node> statusGraphic;
 
 	private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
+
+	//FXML
 	public ToggleGroup predefinedLocationToggler;
 	public RadioButton iclouddriveRadioButton;
 	public RadioButton dropboxRadioButton;
 	public RadioButton gdriveRadioButton;
 	public RadioButton onedriveRadioButton;
 	public RadioButton customRadioButton;
+	public Label vaultPathStatus;
+	public FontAwesome5IconView goodLocation;
+	public FontAwesome5IconView badLocation;
 
 	@Inject
-	CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ErrorComponent.Builder errorComponent, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
+	CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
 		this.window = window;
 		this.chooseNameScene = chooseNameScene;
 		this.choosePasswordScene = choosePasswordScene;
-		this.errorComponent = errorComponent;
 		this.locationPresets = locationPresets;
 		this.vaultPath = vaultPath;
 		this.vaultName = vaultName;
 		this.resourceBundle = resourceBundle;
-		this.validVaultPath = Bindings.createBooleanBinding(this::isValidVaultPath, vaultPath);
+		this.validVaultPath = Bindings.createBooleanBinding(this::validateVaultPathAndSetStatus, this.vaultPath);
 		this.usePresetPath = new SimpleBooleanProperty();
-		this.warningText = new SimpleStringProperty();
-	}
-
-	private boolean isValidVaultPath() {
-		return vaultPath.get() != null && Files.notExists(vaultPath.get());
+		this.statusText = new SimpleStringProperty();
+		this.statusGraphic = new SimpleObjectProperty<>();
+	}
+
+	private boolean validateVaultPathAndSetStatus() {
+		final Path p = vaultPath.get();
+		if (p == null) {
+			statusText.set("Error: Path is NULL.");
+			statusGraphic.set(badLocation);
+			return false;
+		} else if (!Files.exists(p.getParent())) {
+			statusText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
+			statusGraphic.set(badLocation);
+			return false;
+		} else if (!Files.isWritable(p.getParent())) {
+			statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsNotWritable"));
+			statusGraphic.set(badLocation);
+			return false;
+		} else if (!Files.notExists(p)) {
+			statusText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
+			statusGraphic.set(badLocation);
+			return false;
+		} else {
+			statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsOk"));
+			statusGraphic.set(goodLocation);
+			return true;
+		}
 	}
 
 	@FXML
 	public void initialize() {
 		predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
 		usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
-		vaultPath.addListener(this::vaultPathDidChange);
-	}
-
-	private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
-		if (!Files.notExists(newValue)) {
-			warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
-		} else {
-			warningText.set(null);
-		}
 	}
 
 	private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
@@ -115,21 +132,10 @@ public class CreateNewVaultLocationController implements FxController {
 
 	@FXML
 	public void next() {
-		try {
-			// check if we have write access AND the vaultPath doesn't already exist:
-			assert Files.isDirectory(vaultPath.get().getParent());
-			Path createdDir = Files.createDirectory(vaultPath.get());
-			Files.delete(createdDir); // assert: dir exists and is empty
+		if (validateVaultPathAndSetStatus()) {
 			window.setScene(choosePasswordScene.get());
-		} catch (FileAlreadyExistsException e) {
-			LOG.warn("Can not use already existing vault path {}", vaultPath.get());
-			warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
-		} catch (NoSuchFileException e) {
-			LOG.warn("At least one path component does not exist of path {}", vaultPath.get());
-			warningText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
-		} catch (IOException e) {
-			LOG.error("Failed to create and delete directory at chosen vault path.", e);
-			errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+		} else {
+			validVaultPath.invalidate();
 		}
 	}
 
@@ -179,19 +185,27 @@ public class CreateNewVaultLocationController implements FxController {
 		return usePresetPath.get();
 	}
 
-	public StringProperty warningTextProperty() {
-		return warningText;
+	public BooleanBinding anyRadioButtonSelectedProperty() {
+		return predefinedLocationToggler.selectedToggleProperty().isNotNull();
+	}
+
+	public boolean isAnyRadioButtonSelected() {
+		return anyRadioButtonSelectedProperty().get();
+	}
+
+	public StringProperty statusTextProperty() {
+		return statusText;
 	}
 
-	public String getWarningText() {
-		return warningText.get();
+	public String getStatusText() {
+		return statusText.get();
 	}
 
-	public BooleanBinding showWarningProperty() {
-		return warningText.isNotEmpty();
+	public ObjectProperty<Node> statusGraphicProperty() {
+		return statusGraphic;
 	}
 
-	public boolean isShowWarning() {
-		return showWarningProperty().get();
+	public Node getStatusGraphic() {
+		return statusGraphic.get();
 	}
 }

+ 4 - 6
main/ui/src/main/resources/fxml/addvault_new_location.fxml

@@ -20,6 +20,8 @@
 	  alignment="CENTER_LEFT">
 	<fx:define>
 		<ToggleGroup fx:id="predefinedLocationToggler"/>
+		<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" />
+		<FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" />
 	</fx:define>
 	<padding>
 		<Insets topRightBottomLeft="24"/>
@@ -47,12 +49,8 @@
 
 		<VBox spacing="6">
 			<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
-			<TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" disable="true" HBox.hgrow="ALWAYS"/>
-			<Label text="${controller.warningText}" wrapText="true" visible="${controller.showWarning}">
-				<graphic>
-					<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
-				</graphic>
-			</Label>
+			<TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/>
+			<Label fx:id="vaultPathStatus" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" text="${controller.statusText}" graphic="${controller.statusGraphic}" />
 		</VBox>
 
 		<Region VBox.vgrow="ALWAYS"/>

+ 4 - 2
main/ui/src/main/resources/i18n/strings.properties

@@ -45,8 +45,10 @@ addvaultwizard.new.locationPrompt=…
 addvaultwizard.new.directoryPickerLabel=Custom Location
 addvaultwizard.new.directoryPickerButton=Choose…
 addvaultwizard.new.directoryPickerTitle=Select Directory
-addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
-addvaultwizard.new.locationDoesNotExist=Vault can not be created at this path because at least one path component does not exist.
+addvaultwizard.new.fileAlreadyExists=A file or directory with the vault name already exists
+addvaultwizard.new.locationDoesNotExist=A directory in the specified path does not exist or cannot be accessed
+addvaultwizard.new.locationIsNotWritable=No write access at the specified path
+addvaultwizard.new.locationIsOk=Suitable location for your vault
 addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
 ### Password
 addvaultwizard.new.createVaultBtn=Create Vault