Re-layouted unlock UI and added textfield for custom mount flags (atm only supported for FUSE - see #802)

This commit is contained in:
Sebastian Stenzel
2019-06-18 16:39:56 +02:00
parent ed9adab9b4
commit b15d410378
12 changed files with 226 additions and 130 deletions

View File

@@ -11,6 +11,7 @@ package org.cryptomator.ui.controllers;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
@@ -27,6 +28,7 @@ import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
@@ -113,6 +115,12 @@ public class UnlockController implements ViewController {
@FXML
private TextField mountName;
@FXML
private CheckBox useCustomMountFlags;
@FXML
private TextField mountFlags;
@FXML
private CheckBox revealAfterMount;
@@ -134,17 +142,14 @@ public class UnlockController implements ViewController {
@FXML
private ProgressIndicator progressIndicator;
@FXML
private Text progressText;
@FXML
private Hyperlink downloadsPageLink;
@FXML
private GridPane advancedOptions;
private VBox advancedOptions;
@FXML
private GridPane root;
private VBox root;
@FXML
private CheckBox unlockAfterStartup;
@@ -158,6 +163,9 @@ public class UnlockController implements ViewController {
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange);
useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange);
mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not());
mountFlags.textProperty().addListener(this::mountFlagsDidChange);
savePassword.setDisable(!keychainAccess.isPresent());
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
@@ -173,7 +181,6 @@ public class UnlockController implements ViewController {
}
}
@Override
public Parent getRoot() {
return root;
@@ -199,7 +206,6 @@ public class UnlockController implements ViewController {
advancedOptions.setVisible(false);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
progressIndicator.setVisible(false);
progressText.setText(null);
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
if (SystemUtils.IS_OS_WINDOWS) {
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
@@ -212,6 +218,8 @@ public class UnlockController implements ViewController {
}
downloadsPageLink.setVisible(false);
mountName.setText(vault.getMountName());
useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags());
mountFlags.setText(vault.getMountFlags());
savePassword.setSelected(false);
// auto-fill pw from keychain:
if (keychainAccess.isPresent()) {
@@ -318,6 +326,23 @@ public class UnlockController implements ViewController {
} else {
vault.setMountName(newValue);
}
if (!useCustomMountFlags.isSelected()) {
mountFlags.setText(vault.getMountFlags()); // flags might depend on the volume name
}
}
private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused")Boolean oldValue, Boolean newValue) {
if (!newValue) {
vault.setMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS);
mountFlags.setText(vault.getMountFlags());
}
}
private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused")String oldValue, String newValue) {
if (useCustomMountFlags.isSelected()) {
vault.setMountFlags(newValue);
}
}
@FXML
@@ -435,7 +460,6 @@ public class UnlockController implements ViewController {
CharSequence password = passwordField.getCharacters();
Tasks.create(() -> {
progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
vault.unlock(password);
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
@@ -476,7 +500,6 @@ public class UnlockController implements ViewController {
}).andFinally(() -> {
advancedOptions.setDisable(false);
progressIndicator.setVisible(false);
progressText.setText(null);
}).runOnce(executor);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.model;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface DefaultMountFlags {
}

View File

@@ -44,7 +44,7 @@ public class DokanyVolume implements Volume {
}
@Override
public void mount(CryptoFileSystem fs) throws VolumeException, IOException {
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
Path mountPath = getMountPoint();
String mountName = vaultSettings.mountName().get();
try {

View File

@@ -1,5 +1,6 @@
package org.cryptomator.ui.model;
import com.google.common.base.Splitter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
@@ -21,7 +22,6 @@ import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
public class FuseVolume implements Volume {
@@ -44,7 +44,7 @@ public class FuseVolume implements Volume {
}
@Override
public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
if (optionalCustomMountPoint.isPresent()) {
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
@@ -55,7 +55,7 @@ public class FuseVolume implements Volume {
this.mountPoint = prepareTemporaryMountPoint();
LOG.debug("Successfully created mount point: {}", mountPoint);
}
mount(fs.getPath("/"));
mount(fs.getPath("/"), mountFlags);
}
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
@@ -96,11 +96,11 @@ public class FuseVolume implements Volume {
throw new VolumeException("Did not find feasible mount point.");
}
private void mount(Path root) throws VolumeException {
private void mount(Path root, String mountFlags) throws VolumeException {
try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create() //
.withFlags(mountFlags(mounter))
.withFlags(splitFlags(mountFlags))
.withMountPoint(mountPoint) //
.build();
this.fuseMnt = mounter.mount(root, envVars);
@@ -109,13 +109,8 @@ public class FuseVolume implements Volume {
}
}
private String[] mountFlags(Mounter mounter) {
List<String> mountFlags = vaultSettings.mountFlags().get();
if (mountFlags.isEmpty()) {
return mounter.defaultMountFlags();
} else {
return mountFlags.toArray(String[]::new);
}
private String[] splitFlags(String str) {
return Splitter.on(' ').splitToList(str).toArray(String[]::new);
}
@Override

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.model;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface PerVault {
}

View File

@@ -25,7 +25,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.model.VaultModule.PerVault;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,6 +43,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.function.Supplier;
@PerVault
public class Vault {
@@ -54,6 +54,7 @@ public class Vault {
private final VaultSettings vaultSettings;
private final Provider<Volume> volumeProvider;
private final Supplier<String> defaultMountFlags;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
@@ -64,9 +65,10 @@ public class Vault {
}
@Inject
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags Supplier<String> defaultMountFlags) {
this.vaultSettings = vaultSettings;
this.volumeProvider = volumeProvider;
this.defaultMountFlags = defaultMountFlags;
}
// ******************************************************************************
@@ -110,7 +112,7 @@ public class Vault {
}
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
volume = volumeProvider.get();
volume.mount(fs);
volume.mount(fs, getMountFlags());
Platform.runLater(() -> {
state.set(State.UNLOCKED);
});
@@ -241,10 +243,6 @@ public class Vault {
}
}
public String getMountName() {
return vaultSettings.mountName().get();
}
public String getCustomMountPath() {
return vaultSettings.individualMountPath().getValueSafe();
}
@@ -253,6 +251,10 @@ public class Vault {
vaultSettings.individualMountPath().set(mountPath);
}
public String getMountName() {
return vaultSettings.mountName().get();
}
public void setMountName(String mountName) throws IllegalArgumentException {
if (StringUtils.isBlank(mountName)) {
throw new IllegalArgumentException("mount name is empty");
@@ -261,6 +263,23 @@ public class Vault {
}
}
public boolean isHavingCustomMountFlags() {
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
}
public String getMountFlags() {
String mountFlags = vaultSettings.mountFlags().get();
if (Strings.isNullOrEmpty(mountFlags)) {
return defaultMountFlags.get();
} else {
return mountFlags;
}
}
public void setMountFlags(String mountFlags) {
vaultSettings.mountFlags().set(mountFlags);
}
public Character getWinDriveLetter() {
if (vaultSettings.winDriveLetter().get() == null) {
return null;

View File

@@ -7,7 +7,6 @@ package org.cryptomator.ui.model;
import dagger.BindsInstance;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.model.VaultModule.PerVault;
import dagger.Subcomponent;

View File

@@ -7,28 +7,24 @@ package org.cryptomator.ui.model;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Supplier;
@Module
public class VaultModule {
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface PerVault {
}
@Provides
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
@@ -45,4 +41,65 @@ public class VaultModule {
}
}
@Provides
@PerVault
@DefaultMountFlags
public Supplier<String> provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
switch (preferredImpl) {
case FUSE:
if (SystemUtils.IS_OS_MAC_OSX) {
return () -> getMacFuseDefaultMountFlags(settings, vaultSettings);
} else if (SystemUtils.IS_OS_LINUX) {
return () -> getLinuxFuseDefaultMountFlags(settings, vaultSettings);
}
case DOKANY:
return () -> getDokanyDefaultMountFlags(settings, vaultSettings);
default:
return () -> "--flags-supported-on-FUSE-or-DOKANY-only";
}
}
private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
assert SystemUtils.IS_OS_MAC_OSX;
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
try {
Path userHome = Paths.get(System.getProperty("user.home"));
int uid = (int) Files.getAttribute(userHome, "unix:uid");
int gid = (int) Files.getAttribute(userHome, "unix:gid");
return "-ovolname=" + vaultSettings.mountName().get() // volume name
+ " -ouid=" + uid //
+ " -ogid=" + gid //
+ " -oatomic_o_trunc" //
+ " -oauto_xattr" //
+ " -oauto_cache" //
+ " -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC" // show files names in Unicode NFD encoding
+ " -onoappledouble" // vastly impacts performance for some reason...
+ " -odefault_permissions"; // let the kernel assume permissions based on file attributes etc
} catch (IOException e) {
LOG.error("Could not read uid/gid from USER_HOME", e);
return "";
}
}
private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
assert SystemUtils.IS_OS_LINUX;
try {
Path userHome = Paths.get(System.getProperty("user.home"));
int uid = (int) Files.getAttribute(userHome, "unix:uid");
int gid = (int) Files.getAttribute(userHome, "unix:gid");
return "-oauto_unmount" //
+ " -ouid=" + uid //
+ " -ogid=" + gid;
} catch (IOException e) {
LOG.error("Could not read uid/gid from USER_HOME", e);
return "";
}
}
private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
// TODO
return "--not-yet-supported";
}
}

View File

@@ -22,7 +22,7 @@ public interface Volume {
* @param fs
* @throws IOException
*/
void mount(CryptoFileSystem fs) throws IOException, VolumeException;
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException;
void reveal() throws VolumeException;

View File

@@ -11,7 +11,6 @@ import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
import javax.inject.Inject;
import javax.inject.Provider;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -35,7 +34,7 @@ public class WebDavVolume implements Volume {
}
@Override
public void mount(CryptoFileSystem fs) throws VolumeException {
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
if (server == null) {
server = serverProvider.get();
}

View File

@@ -17,102 +17,81 @@
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<?import org.cryptomator.ui.controls.SecPasswordField?>
<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<VBox fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" spacing="12" alignment="BOTTOM_CENTER" xmlns:fx="http://javafx.com/fxml" prefWidth="400">
<padding>
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
<Insets top="24"/>
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="38.2"/>
<ColumnConstraints percentWidth="61.8"/>
</columnConstraints>
<!-- Password Field -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<Label text="%unlock.label.password" HBox.hgrow="NEVER"/>
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" maxWidth="Infinity" HBox.hgrow="ALWAYS"/>
</HBox>
<children>
<!-- Row 0 -->
<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Unlock Button / Advanced Options Button -->
<HBox spacing="12.0" alignment="CENTER_RIGHT">
<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton"/>
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true"/>
<ProgressIndicator progress="-1" fx:id="progressIndicator"/>
</HBox>
<!-- Row 1 -->
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton" cacheShape="true" cache="true" />
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true" cacheShape="true" cache="true" />
<!-- Advanced Options -->
<VBox fx:id="advancedOptions" spacing="6" VBox.vgrow="ALWAYS" visible="false">
<Separator/>
<!-- Mount Name -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<Label text="%unlock.label.mountName"/>
<TextField fx:id="mountName" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
</HBox>
<!-- Row 3 -->
<Text fx:id="messageText" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
<!-- Save Password -->
<CheckBox fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox"/>
<!-- Row 3 -->
<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
<!-- Auto Unlock -->
<CheckBox fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup"/>
<!-- Reveal Drive -->
<CheckBox fx:id="revealAfterMount" text="%unlock.label.revealAfterMount"/>
<!-- Read-Only -->
<CheckBox fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode"/>
<!-- Custom Mount Point -->
<CheckBox fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath"/>
<HBox fx:id="customMountPoint" spacing="6" alignment="BASELINE_LEFT">
<padding>
<Insets top="24.0" />
<Insets left="20.0"/>
</padding>
<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS"/>
<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="&#xf434;" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true"/>
</HBox>
<columnConstraints>
<ColumnConstraints percentWidth="38.2"/>
<ColumnConstraints percentWidth="61.8"/>
</columnConstraints>
<!-- Mount Flags -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<CheckBox fx:id="useCustomMountFlags" text="%unlock.label.useCustomMountFlags"/>
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
</HBox>
<!-- Row 3.0 -->
<Separator GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true"/>
<HBox alignment="CENTER" prefWidth="400.0" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
<Label text="%unlock.label.advancedHeading" style="-fx-background-color: COLOR_BACKGROUND;" cacheShape="true" cache="true">
<padding>
<Insets left="6.0" right="6.0"/>
</padding>
</Label>
</HBox>
<!-- Windows Drive Letter -->
<HBox spacing="12">
<Label fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter"/>
<ChoiceBox fx:id="winDriveLetter" maxWidth="Infinity"/>
</HBox>
</VBox>
<!-- Row 3.1 -->
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
<!-- Row 3.2 -->
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
<!-- Row 3.3 -->
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.4 -->
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
<!-- Row 3.5 -->
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode" cacheShape="true" cache="true" />
<!-- Row 3.6 -->
<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
<!-- Row 3.7 Alt1 -->
<Label GridPane.rowIndex="7" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.7 Alt2 -->
<HBox fx:id="customMountPoint" GridPane.rowIndex="7" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="6" alignment="BASELINE_LEFT" cacheShape="true" cache="true">
<padding>
<Insets left="20.0" />
</padding>
<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS" cacheShape="true" cache="true" />
<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="&#xf434;" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true" cacheShape="true" cache="true" />
</HBox>
</GridPane>
<!-- Row 4 -->
<TextFlow GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
<GridPane.margin>
<Insets top="24.0"/>
</GridPane.margin>
<children>
<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
</children>
</TextFlow>
<!-- Row 5 -->
<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
<ProgressIndicator progress="-1" fx:id="progressIndicator" cacheShape="true" cache="true" cacheHint="SPEED" />
<Text fx:id="progressText" cache="true" />
</VBox>
</children>
</GridPane>
<!-- Status Text -->
<TextFlow>
<children>
<Text fx:id="messageText" visible="true"/>
<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink"/>
</children>
</TextFlow>
</VBox>

View File

@@ -74,14 +74,14 @@ upgrade.version5toX.msg=This vault needs to be migrated to a newer format.\nPlea
unlock.label.password=Password
unlock.label.savePassword=Save Password
unlock.label.mountName=Drive Name
unlock.label.useCustomMountFlags=Custom Mount Flags
unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
unlock.label.revealAfterMount=Reveal Drive
unlock.label.useReadOnlyMode=Read-Only
unlock.label.winDriveLetter=Drive Letter
unlock.label.useOwnMountPath=Use Custom Mount Point
unlock.label.useOwnMountPath=Custom Mount Point
unlock.label.chooseMountPath=Choose empty directory…
unlock.label.downloadsPageLink=All Cryptomator versions
unlock.label.advancedHeading=Advanced Options
unlock.button.unlock=Unlock Vault
unlock.button.advancedOptions.show=More Options
unlock.button.advancedOptions.hide=Less Options
@@ -89,7 +89,6 @@ unlock.savePassword.delete.confirmation.title=Delete Saved Password
unlock.savePassword.delete.confirmation.header=Do you really want to delete the saved password of this vault?
unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
unlock.choicebox.winDriveLetter.auto=Assign automatically
unlock.pendingMessage.unlocking=Unlocking vault...
unlock.errorMessage.wrongPassword=Wrong password
unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.