mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-16 17:51:27 +00:00
Merge branch 'release/1.5.0-beta3'
This commit is contained in:
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [overheadhunter, tobihagemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [cryptomator] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
12
.idea/compiler.xml
generated
12
.idea/compiler.xml
generated
@@ -7,20 +7,20 @@
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.25.2/dagger-compiler-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.25.2/dagger-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.26/dagger-compiler-2.26.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.26/dagger-2.26.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.25.2/dagger-producers-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.26/dagger-producers-2.26.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.25.2/dagger-spi-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.26/dagger-spi-2.26.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
@@ -32,9 +32,9 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
||||
</processorPath>
|
||||
<module name="keychain" />
|
||||
<module name="launcher" />
|
||||
<module name="commons" />
|
||||
<module name="ui" />
|
||||
<module name="launcher" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
@@ -19,6 +18,8 @@ import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
@@ -27,12 +28,18 @@ import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Module(subcomponents = {VaultComponent.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||
private static final int NUM_SCHEDULER_THREADS = 2;
|
||||
private static final int NUM_CORE_BG_THREADS = 6;
|
||||
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@@ -69,18 +76,38 @@ public abstract class CommonsModule {
|
||||
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||
String name = String.format("App Scheduled Executor %02d", threadNumber.getAndIncrement());
|
||||
Thread t = new Thread(r);
|
||||
t.setName("Background Thread " + threadNumber.getAndIncrement());
|
||||
t.setName(name);
|
||||
t.setUncaughtExceptionHandler(CommonsModule::handleUncaughtExceptionInBackgroundThread);
|
||||
t.setDaemon(true);
|
||||
LOG.debug("Starting {}", t.getName());
|
||||
return t;
|
||||
});
|
||||
shutdownHook.runOnShutdown(executorService::shutdown);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
@Binds
|
||||
@Provides
|
||||
@Singleton
|
||||
abstract ExecutorService bindExecutorService(ScheduledExecutorService executor);
|
||||
static ExecutorService provideExecutorService(ShutdownHook shutdownHook) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ExecutorService executorService = new ThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
|
||||
String name = String.format("App Background Thread %03d", threadNumber.getAndIncrement());
|
||||
Thread t = new Thread(r);
|
||||
t.setName(name);
|
||||
t.setUncaughtExceptionHandler(CommonsModule::handleUncaughtExceptionInBackgroundThread);
|
||||
t.setDaemon(true);
|
||||
LOG.debug("Starting {}", t.getName());
|
||||
return t;
|
||||
});
|
||||
shutdownHook.runOnShutdown(executorService::shutdown);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
private static void handleUncaughtExceptionInBackgroundThread(Thread thread, Throwable throwable) {
|
||||
LOG.error("Uncaught exception in " + thread.getName(), throwable);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -46,16 +45,17 @@ public class SettingsProvider implements Supplier<Settings> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
||||
private final Environment env;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public SettingsProvider(Environment env) {
|
||||
public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
|
||||
this.env = env;
|
||||
this.scheduler = scheduler;
|
||||
this.gson = new GsonBuilder() //
|
||||
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
||||
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
||||
@@ -98,7 +98,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
|
||||
settingsPath.ifPresent(path -> {
|
||||
Runnable saveCommand = () -> this.save(settings, path);
|
||||
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> scheduledTask = scheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
||||
if (previouslyScheduledTask != null) {
|
||||
previouslyScheduledTask.cancel(false);
|
||||
|
||||
@@ -41,7 +41,6 @@ public final class WindowsDriveLetters {
|
||||
|
||||
public Set<String> getOccupiedDriveLetters() {
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
|
||||
return Set.of();
|
||||
} else {
|
||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
@@ -11,6 +11,7 @@ import dagger.Provides;
|
||||
import dagger.multibindings.ElementsIntoSet;
|
||||
import org.cryptomator.common.JniModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -24,6 +25,7 @@ public class KeychainModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
|
||||
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -21,8 +21,10 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
@@ -40,7 +42,7 @@ class FileOpenRequestHandler {
|
||||
}
|
||||
|
||||
private void openFiles(OpenFilesEvent evt) {
|
||||
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
|
||||
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
@@ -51,16 +53,18 @@ class FileOpenRequestHandler {
|
||||
|
||||
// visible for testing
|
||||
void handleLaunchArgs(FileSystem fs, String[] args) {
|
||||
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
|
||||
Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
|
||||
try {
|
||||
return fs.getPath(str);
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.trace("Argument not a valid path: {}", str);
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (!pathsToOpen.isEmpty()) {
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -27,7 +28,7 @@ class IpcProtocolImpl implements IpcProtocol {
|
||||
|
||||
@Override
|
||||
public void revealRunningApp() {
|
||||
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Stream.empty()));
|
||||
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -55,10 +55,11 @@ public class LoggerConfiguration {
|
||||
}
|
||||
|
||||
// configure upgrade logger:
|
||||
Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
|
||||
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
|
||||
upgrades.setLevel(Level.DEBUG);
|
||||
upgrades.addAppender(stdout);
|
||||
upgrades.addAppender(upgrade);
|
||||
upgrades.addAppender(file);
|
||||
upgrades.setAdditive(false);
|
||||
|
||||
// add shutdown hook
|
||||
|
||||
@@ -15,16 +15,14 @@ import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileOpenRequestHandlerTest {
|
||||
|
||||
@@ -39,32 +37,30 @@ public class FileOpenRequestHandlerTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo bar")
|
||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
||||
public void testOpenArgsWithCorrectPaths() {
|
||||
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
|
||||
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
Collection<Path> paths = evt.getPathsToOpen();
|
||||
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
|
||||
public void testOpenArgsWithIncorrectPaths() throws IOException {
|
||||
public void testOpenArgsWithIncorrectPaths() {
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
|
||||
inTest.handleLaunchArgs(fs, new String[]{"foo"});
|
||||
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
Assertions.assertTrue(paths.isEmpty());
|
||||
Assertions.assertNull(evt);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||
public void testOpenArgsWithFullQueue() throws IOException {
|
||||
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Stream.empty()));
|
||||
public void testOpenArgsWithFullQueue() {
|
||||
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
|
||||
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||
|
||||
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||
|
||||
16
main/pom.xml
16
main/pom.xml
@@ -3,13 +3,13 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
<organization>
|
||||
<name>cryptomator.org</name>
|
||||
<url>http://cryptomator.org</url>
|
||||
<url>https://cryptomator.org</url>
|
||||
</organization>
|
||||
|
||||
<developers>
|
||||
@@ -24,33 +24,33 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>1.9.3</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<javafx.version>13.0.1</javafx.version>
|
||||
<javafx.version>14-ea+8</javafx.version>
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
<jwt.version>3.8.3</jwt.version>
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<dagger.version>2.25.2</dagger.version>
|
||||
<dagger.version>2.26</dagger.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
<slf4j.version>1.7.29</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||
<mockito.version>3.1.0</mockito.version>
|
||||
<junit.jupiter.version>5.6.0</junit.jupiter.version>
|
||||
<mockito.version>3.2.4</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<url>http://jcenter.bintray.com</url>
|
||||
<url>https://jcenter.bintray.com</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta2</version>
|
||||
<version>1.5.0-beta3</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
@@ -27,8 +27,8 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -51,13 +51,13 @@ public abstract class AddVaultModule {
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -193,8 +193,8 @@ public abstract class AddVaultModule {
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyDisplayController.class)
|
||||
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey) {
|
||||
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get());
|
||||
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey, ResourceBundle localization) {
|
||||
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get(), localization);
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ReadmeGenerator {
|
||||
// specs: https://web.archive.org/web/20190708132914/http://www.kleinlercher.at/tools/Windows_Protocols/Word2007RTFSpec9.pdf
|
||||
private static final String RTF_HEADER = "{\\rtf1\\fbidis\\ansi\\uc0\\fs32\n";
|
||||
private static final String RTF_FOOTER = "}";
|
||||
private static final String HELP_URL = "{\\field{\\*\\fldinst HYPERLINK \"http://www.google.com/\"}{\\fldrslt google.com}}";
|
||||
private static final String HELP_URL = "{\\field{\\*\\fldinst HYPERLINK \"http://docs.cryptoamtor.org/\"}{\\fldrslt docs.cryptoamtor.org}}";
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -46,13 +46,13 @@ abstract class ChangePasswordModule {
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@ChangePasswordScoped
|
||||
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("changepassword.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class Animations {
|
||||
|
||||
public static Timeline createShakeWindowAnimation(Window window) {
|
||||
WritableValue<Double> writableWindowX = new WritableValue<>() {
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return window.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double value) {
|
||||
window.setX(value);
|
||||
}
|
||||
};
|
||||
return new Timeline( //
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
|
||||
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
|
||||
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
|
||||
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
|
||||
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
|
||||
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
|
||||
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
|
||||
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,12 +12,16 @@ public enum FxmlFile {
|
||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
||||
MIGRATION_CAPABILITY_ERROR("/fxml/migration_capability_error.fxml"), //
|
||||
MIGRATION_GENERIC_ERROR("/fxml/migration_generic_error.fxml"), //
|
||||
MIGRATION_RUN("/fxml/migration_run.fxml"), //
|
||||
MIGRATION_START("/fxml/migration_start.fxml"), //
|
||||
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
|
||||
PREFERENCES("/fxml/preferences.fxml"), //
|
||||
QUIT("/fxml/quit.fxml"), //
|
||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||
UNLOCK("/fxml/unlock.fxml"),
|
||||
|
||||
@@ -8,11 +8,11 @@ public class StackTraceController implements FxController {
|
||||
|
||||
private final String stackTrace;
|
||||
|
||||
public StackTraceController(Exception cause) {
|
||||
public StackTraceController(Throwable cause) {
|
||||
this.stackTrace = provideStackTrace(cause);
|
||||
}
|
||||
|
||||
static String provideStackTrace(Exception cause) {
|
||||
private static String provideStackTrace(Throwable cause) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
cause.printStackTrace(new PrintStream(baos));
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
|
||||
@@ -73,21 +73,21 @@ public class Tasks {
|
||||
return new TaskImpl<>(callable, successHandler, errorHandlers, finallyHandler);
|
||||
}
|
||||
|
||||
public Task<T> runOnce(ExecutorService executorService) {
|
||||
public Task<T> runOnce(ExecutorService executor) {
|
||||
Task<T> task = build();
|
||||
executorService.submit(task);
|
||||
executor.submit(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public Task<T> scheduleOnce(ScheduledExecutorService executorService, long delay, TimeUnit unit) {
|
||||
public Task<T> scheduleOnce(ScheduledExecutorService scheduler, long delay, TimeUnit unit) {
|
||||
Task<T> task = build();
|
||||
executorService.schedule(task, delay, unit);
|
||||
scheduler.schedule(task, delay, unit);
|
||||
return task;
|
||||
}
|
||||
|
||||
public ScheduledService<T> schedulePeriodically(ExecutorService executorService, Duration initialDelay, Duration period) {
|
||||
public ScheduledService<T> schedulePeriodically(ExecutorService executor, Duration initialDelay, Duration period) {
|
||||
ScheduledService<T> service = new RestartingService<>(this::build);
|
||||
service.setExecutor(executorService);
|
||||
service.setExecutor(executor);
|
||||
service.setDelay(initialDelay);
|
||||
service.setPeriod(period);
|
||||
Platform.runLater(service::start);
|
||||
|
||||
@@ -4,15 +4,20 @@ import javafx.concurrent.Task;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -23,10 +28,12 @@ public class VaultService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final Optional<KeychainAccess> keychain;
|
||||
|
||||
@Inject
|
||||
public VaultService(ExecutorService executorService) {
|
||||
public VaultService(ExecutorService executorService, Optional<KeychainAccess> keychain) {
|
||||
this.executorService = executorService;
|
||||
this.keychain = keychain;
|
||||
}
|
||||
|
||||
public void reveal(Vault vault) {
|
||||
@@ -45,6 +52,72 @@ public class VaultService {
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to unlock all given vaults in a background thread using passwords stored in the system keychain.
|
||||
*
|
||||
* @param vaults The vaults to unlock
|
||||
* @implNote No-op if no system keychain is present
|
||||
*/
|
||||
public void attemptAutoUnlock(Collection<Vault> vaults) {
|
||||
if (!keychain.isPresent()) {
|
||||
LOG.debug("No system keychain found. Unable to auto unlock without saved passwords.");
|
||||
} else {
|
||||
for (Vault vault : vaults) {
|
||||
attemptAutoUnlock(vault, keychain.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks a vault in a background thread using a stored passphrase
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param keychainAccess The system keychain holding the passphrase for the vault
|
||||
*/
|
||||
public void attemptAutoUnlock(Vault vault, KeychainAccess keychainAccess) {
|
||||
executorService.execute(createAutoUnlockTask(vault, keychainAccess));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates but doesn't start an auto-unlock task.
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param keychainAccess The system keychain holding the passphrase for the vault
|
||||
* @return The task
|
||||
*/
|
||||
public Task<Vault> createAutoUnlockTask(Vault vault, KeychainAccess keychainAccess) {
|
||||
Task<Vault> task = new AutoUnlockVaultTask(vault, keychainAccess);
|
||||
task.setOnSucceeded(evt -> LOG.info("Auto-unlocked {}", vault.getDisplayableName()));
|
||||
task.setOnFailed(evt -> LOG.error("Failed to auto-unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks a vault in a background thread
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public void unlock(Vault vault, CharSequence passphrase) {
|
||||
executorService.execute(createUnlockTask(vault, passphrase));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates but doesn't start an unlock task.
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @return The task
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public Task<Vault> createUnlockTask(Vault vault, CharSequence passphrase) {
|
||||
Task<Vault> task = new UnlockVaultTask(vault, passphrase);
|
||||
task.setOnSucceeded(evt -> LOG.info("Unlocked {}", vault.getDisplayableName()));
|
||||
task.setOnFailed(evt -> LOG.error("Failed to unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks a vault in a background thread.
|
||||
*
|
||||
@@ -60,6 +133,7 @@ public class VaultService {
|
||||
*
|
||||
* @param vault The vault to lock
|
||||
* @param forced Whether to attempt a forced lock
|
||||
* @return The task
|
||||
*/
|
||||
public Task<Vault> createLockTask(Vault vault, boolean forced) {
|
||||
Task<Vault> task = new LockVaultTask(vault, forced);
|
||||
@@ -145,6 +219,93 @@ public class VaultService {
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutoUnlockVaultTask extends Task<Vault> {
|
||||
|
||||
private final Vault vault;
|
||||
private final KeychainAccess keychain;
|
||||
|
||||
public AutoUnlockVaultTask(Vault vault, KeychainAccess keychain) {
|
||||
this.vault = vault;
|
||||
this.keychain = keychain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vault call() throws Exception {
|
||||
char[] storedPw = null;
|
||||
try {
|
||||
storedPw = keychain.loadPassphrase(vault.getId());
|
||||
if (storedPw == null) {
|
||||
throw new InvalidPassphraseException();
|
||||
}
|
||||
vault.unlock(CharBuffer.wrap(storedPw));
|
||||
} finally {
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnlockVaultTask extends Task<Vault> {
|
||||
|
||||
private final Vault vault;
|
||||
private final CharBuffer passphrase;
|
||||
|
||||
/**
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public UnlockVaultTask(Vault vault, CharSequence passphrase) {
|
||||
this.vault = vault;
|
||||
this.passphrase = CharBuffer.allocate(passphrase.length());
|
||||
for (int i = 0; i < passphrase.length(); i++) {
|
||||
this.passphrase.put(i, passphrase.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vault call() throws Exception {
|
||||
try {
|
||||
vault.unlock(passphrase);
|
||||
} finally {
|
||||
Arrays.fill(passphrase.array(), ' ');
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that locks a vault
|
||||
*/
|
||||
@@ -186,5 +347,4 @@ public class VaultService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ package org.cryptomator.ui.controls;
|
||||
*/
|
||||
public enum FontAwesome5Icon {
|
||||
ANCHOR("\uF13D"), //
|
||||
ARROW_ALT_UP("\uF357"), //
|
||||
ARROW_UP("\uF062"), //
|
||||
CHECK("\uF00C"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COPY("\uF0C5"), //
|
||||
EXCLAMATION("\uF12A"),
|
||||
CROWN("\uF521"), //
|
||||
EXCLAMATION("\uF12A"), //
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
EXCLAMATION_TRIANGLE("\uF071"), //
|
||||
EYE("\uF06E"), //
|
||||
@@ -22,17 +23,17 @@ public enum FontAwesome5Icon {
|
||||
HDD("\uF0A0"), //
|
||||
KEY("\uF084"), //
|
||||
LINK("\uF0C1"), //
|
||||
LOCK_ALT("\uF30D"), //
|
||||
LOCK_OPEN_ALT("\uF3C2"), //
|
||||
LOCK("\uF023"), //
|
||||
LOCK_OPEN("\uF3C1"), //
|
||||
MAGIC("\uF0D0"), //
|
||||
PLUS("\uF067"), //
|
||||
PRINT("\uF02F"), //
|
||||
QUESTION("\uF128"), //
|
||||
SPARKLES("\uF890"), //
|
||||
SPINNER("\uF110"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
USER_CROWN("\uF6A4"), //
|
||||
WRENCH("\uF0AD"), //
|
||||
WINDOW_MINIMIZE("\uF2D1"), //
|
||||
;
|
||||
|
||||
private final String unicode;
|
||||
|
||||
@@ -18,7 +18,7 @@ public class FontAwesome5IconView extends Text {
|
||||
|
||||
private static final FontAwesome5Icon DEFAULT_GLYPH = FontAwesome5Icon.ANCHOR;
|
||||
private static final double DEFAULT_GLYPH_SIZE = 12.0;
|
||||
private static final String FONT_PATH = "/css/fontawesome5-pro-solid.otf";
|
||||
private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf";
|
||||
private static final Font FONT;
|
||||
|
||||
private ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
iconContainer.getStyleClass().add(ICONS_STLYE_CLASS);
|
||||
StackPane.setAlignment(iconContainer, Pos.CENTER_RIGHT);
|
||||
|
||||
capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_ALT_UP);
|
||||
capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_UP);
|
||||
capsLockedIcon.setGlyphSize(ICON_SIZE);
|
||||
capsLockedIcon.visibleProperty().bind(passwordField.capsLockedProperty());
|
||||
capsLockedIcon.managedProperty().bind(passwordField.capsLockedProperty());
|
||||
|
||||
@@ -20,8 +20,8 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -37,13 +37,13 @@ abstract class ForgetPasswordModule {
|
||||
@Provides
|
||||
@ForgetPasswordWindow
|
||||
@ForgetPasswordScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("forgetPasswordOwner") Stage owner) {
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("forgetPasswordOwner") Stage owner) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("forgetPassword.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, QuitComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
@@ -34,19 +36,29 @@ abstract class FxApplicationModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("windowIcon")
|
||||
@Named("windowIcons")
|
||||
@FxApplicationScoped
|
||||
static Optional<Image> provideWindowIcon() {
|
||||
static List<Image> provideWindowIcons() {
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
return Optional.empty();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try (InputStream in = FxApplicationModule.class.getResourceAsStream("/window_icon_32.png")) { // TODO: use some higher res depending on display?
|
||||
return Optional.of(new Image(in));
|
||||
|
||||
try {
|
||||
return List.of( //
|
||||
createImageFromResource("/window_icon_32.png"), //
|
||||
createImageFromResource("/window_icon_512.png") //
|
||||
);
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
throw new UncheckedIOException("Failed to load embedded resource.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) {
|
||||
return new Image(in);
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract Application bindApplication(FxApplication application);
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package org.cryptomator.ui.launcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AppLaunchEvent {
|
||||
|
||||
private final Stream<Path> pathsToOpen;
|
||||
private final EventType type;
|
||||
private final Collection<Path> pathsToOpen;
|
||||
|
||||
public enum EventType {REVEAL_APP, OPEN_FILE}
|
||||
|
||||
public AppLaunchEvent(EventType type, Stream<Path> pathsToOpen) {
|
||||
public AppLaunchEvent(EventType type, Collection<Path> pathsToOpen) {
|
||||
this.type = type;
|
||||
this.pathsToOpen = pathsToOpen;
|
||||
}
|
||||
@@ -19,7 +20,7 @@ public class AppLaunchEvent {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Stream<Path> getPathsToOpen() {
|
||||
public Collection<Path> getPathsToOpen() {
|
||||
return pathsToOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.cryptomator.ui.launcher;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.EnumSet;
|
||||
import java.util.EventObject;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Singleton
|
||||
public class AppLifecycleListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
|
||||
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
|
||||
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final AtomicBoolean allowQuitWithoutPrompt;
|
||||
|
||||
@Inject
|
||||
AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
|
||||
this.fxApplicationStarter = fxApplicationStarter;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.vaults = vaults;
|
||||
this.allowQuitWithoutPrompt = new AtomicBoolean(true);
|
||||
vaults.addListener(this::vaultListChanged);
|
||||
|
||||
// register preferences shortcut
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
|
||||
Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
|
||||
}
|
||||
|
||||
// register quit handler
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
||||
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
||||
}
|
||||
|
||||
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully terminates the application.
|
||||
*/
|
||||
public void quit() {
|
||||
handleQuitRequest(null, new QuitResponse() {
|
||||
@Override
|
||||
public void performQuit() {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelQuit() {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
|
||||
QuitResponse decoratedQuitResponse = decorateQuitResponse(response);
|
||||
if (allowQuitWithoutPrompt.get()) {
|
||||
decoratedQuitResponse.performQuit();
|
||||
} else {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
|
||||
}
|
||||
}
|
||||
|
||||
private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) {
|
||||
return new QuitResponse() {
|
||||
@Override
|
||||
public void performQuit() {
|
||||
Platform.exit(); // will be no-op, if JavaFX never started.
|
||||
shutdownLatch.countDown(); // main thread is waiting for this latch
|
||||
EventQueue.invokeLater(originalQuitResponse::performQuit); // this will eventually call System.exit(0)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelQuit() {
|
||||
originalQuitResponse.cancelQuit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
|
||||
boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
|
||||
if (suddenTerminationChanged) {
|
||||
LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination);
|
||||
}
|
||||
}
|
||||
|
||||
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
}
|
||||
|
||||
private void forceUnmountRemainingVaults() {
|
||||
for (Vault vault : vaults) {
|
||||
if (vault.isUnlocked()) {
|
||||
try {
|
||||
vault.lock(true);
|
||||
} catch (Volume.VolumeException e) {
|
||||
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.cryptomator.ui.launcher;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
@@ -13,9 +15,8 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.desktop.AppReopenedEvent;
|
||||
import java.awt.desktop.AppReopenedListener;
|
||||
import java.awt.desktop.SystemEventListener;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@@ -24,14 +25,16 @@ public class UiLauncher {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UiLauncher.class);
|
||||
|
||||
private final Settings settings;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final TrayMenuComponent.Builder trayComponent;
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final AppLaunchEventHandler launchEventHandler;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
|
||||
@Inject
|
||||
public UiLauncher(Settings settings, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<MacFunctions> macFunctions) {
|
||||
public UiLauncher(Settings settings, ObservableList<Vault> vaults, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<MacFunctions> macFunctions) {
|
||||
this.settings = settings;
|
||||
this.vaults = vaults;
|
||||
this.trayComponent = trayComponent;
|
||||
this.fxApplicationStarter = fxApplicationStarter;
|
||||
this.launchEventHandler = launchEventHandler;
|
||||
@@ -48,7 +51,7 @@ public class UiLauncher {
|
||||
}
|
||||
|
||||
// show window on start?
|
||||
if (settings.startHidden().get()) {
|
||||
if (hasTrayIcon && settings.startHidden().get()) {
|
||||
LOG.debug("Hiding application...");
|
||||
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToAgentApplication));
|
||||
} else {
|
||||
@@ -58,6 +61,12 @@ public class UiLauncher {
|
||||
// register app reopen listener
|
||||
Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));
|
||||
|
||||
// auto unlock
|
||||
Collection<Vault> vaultsWithAutoUnlockEnabled = vaults.filtered(v -> v.getVaultSettings().unlockAfterStartup().get());
|
||||
if (!vaultsWithAutoUnlockEnabled.isEmpty()) {
|
||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> app.getVaultService().attemptAutoUnlock(vaultsWithAutoUnlockEnabled));
|
||||
}
|
||||
|
||||
launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, WrongFileAlertComponent.class})
|
||||
@@ -42,7 +42,7 @@ abstract class MainWindowModule {
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
static Stage provideStage(@Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage(StageStyle.UNDECORATED);
|
||||
// TODO: min/max values chosen arbitrarily. We might wanna take a look at the user's resolution...
|
||||
stage.setMinWidth(650);
|
||||
@@ -50,7 +50,7 @@ abstract class MainWindowModule {
|
||||
stage.setMaxWidth(1000);
|
||||
stage.setMaxHeight(700);
|
||||
stage.setTitle("Cryptomator");
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,11 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||
import org.cryptomator.ui.launcher.AppLifecycleListener;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -24,6 +23,7 @@ public class MainWindowTitleController implements FxController {
|
||||
|
||||
public HBox titleBar;
|
||||
|
||||
private final AppLifecycleListener appLifecycle;
|
||||
private final Stage window;
|
||||
private final FxApplication application;
|
||||
private final boolean minimizeToSysTray;
|
||||
@@ -35,7 +35,8 @@ public class MainWindowTitleController implements FxController {
|
||||
private double yOffset;
|
||||
|
||||
@Inject
|
||||
MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
|
||||
MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
|
||||
this.appLifecycle = appLifecycle;
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.minimizeToSysTray = minimizeToSysTray;
|
||||
@@ -56,6 +57,10 @@ public class MainWindowTitleController implements FxController {
|
||||
window.setX(event.getScreenX() - xOffset);
|
||||
window.setY(event.getScreenY() - yOffset);
|
||||
});
|
||||
window.setOnCloseRequest(event -> {
|
||||
close();
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -63,10 +68,15 @@ public class MainWindowTitleController implements FxController {
|
||||
if (minimizeToSysTray) {
|
||||
window.close();
|
||||
} else {
|
||||
window.setIconified(true);
|
||||
appLifecycle.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void minimize() {
|
||||
window.setIconified(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showPreferences() {
|
||||
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
||||
@@ -91,5 +101,7 @@ public class MainWindowTitleController implements FxController {
|
||||
return updateAvailable.get();
|
||||
}
|
||||
|
||||
|
||||
public boolean isMinimizeToSysTray() {
|
||||
return minimizeToSysTray;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ public class VaultDetailController implements FxController {
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
||||
switch (state) {
|
||||
case LOCKED:
|
||||
return FontAwesome5Icon.LOCK_ALT;
|
||||
return FontAwesome5Icon.LOCK;
|
||||
case PROCESSING:
|
||||
return FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED:
|
||||
return FontAwesome5Icon.LOCK_OPEN_ALT;
|
||||
return FontAwesome5Icon.LOCK_OPEN;
|
||||
default:
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ public class VaultListCellController implements FxController {
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
||||
switch (state) {
|
||||
case LOCKED:
|
||||
return FontAwesome5Icon.LOCK_ALT;
|
||||
return FontAwesome5Icon.LOCK;
|
||||
case PROCESSING:
|
||||
return FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED:
|
||||
return FontAwesome5Icon.LOCK_OPEN_ALT;
|
||||
return FontAwesome5Icon.LOCK_OPEN;
|
||||
default:
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.cryptomator.ui.migration;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@MigrationScoped
|
||||
public class MigrationCapabilityErrorController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final ResourceBundle localization;
|
||||
private final Lazy<Scene> startScene;
|
||||
private final StringBinding missingCapabilityDescription;
|
||||
private final ReadOnlyObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
|
||||
|
||||
@Inject
|
||||
MigrationCapabilityErrorController(@MigrationWindow Stage window, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, ResourceBundle localization, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene) {
|
||||
this.window = window;
|
||||
this.missingCapability = missingCapability;
|
||||
this.localization = localization;
|
||||
this.startScene = startScene;
|
||||
this.missingCapabilityDescription = Bindings.createStringBinding(this::getMissingCapabilityDescription, missingCapability);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(startScene.get());
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
public StringBinding missingCapabilityDescriptionProperty() {
|
||||
return missingCapabilityDescription;
|
||||
}
|
||||
|
||||
public String getMissingCapabilityDescription() {
|
||||
FileSystemCapabilityChecker.Capability c = missingCapability.get();
|
||||
if (c != null) {
|
||||
return localization.getString("migration.error.missingFileSystemCapabilities.reason." + c.name());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.cryptomator.ui.migration;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@MigrationScoped
|
||||
public class MigrationGenericErrorController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> startScene;
|
||||
|
||||
@Inject
|
||||
MigrationGenericErrorController(@MigrationWindow Stage window, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene) {
|
||||
this.window = window;
|
||||
this.startScene = startScene;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(startScene.get());
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,26 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StackTraceController;
|
||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -35,16 +39,30 @@ abstract class MigrationModule {
|
||||
@Provides
|
||||
@MigrationWindow
|
||||
@MigrationScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("migration.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("genericErrorCause")
|
||||
@MigrationScoped
|
||||
static ObjectProperty<Throwable> provideGenericErrorCause() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("capabilityErrorCause")
|
||||
@MigrationScoped
|
||||
static ObjectProperty<FileSystemCapabilityChecker.Capability> provideCapabilityErrorCause() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.MIGRATION_START)
|
||||
@MigrationScoped
|
||||
@@ -66,6 +84,21 @@ abstract class MigrationModule {
|
||||
return fxmlLoaders.createScene("/fxml/migration_success.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
|
||||
@MigrationScoped
|
||||
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/migration_capability_error.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.MIGRATION_GENERIC_ERROR)
|
||||
@MigrationScoped
|
||||
static Scene provideMigrationGenericErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/migration_generic_error.fxml");
|
||||
}
|
||||
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@@ -83,4 +116,21 @@ abstract class MigrationModule {
|
||||
@FxControllerKey(MigrationSuccessController.class)
|
||||
abstract FxController bindMigrationSuccessController(MigrationSuccessController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(MigrationCapabilityErrorController.class)
|
||||
abstract FxController bindMigrationCapabilityErrorController(MigrationCapabilityErrorController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(MigrationGenericErrorController.class)
|
||||
abstract FxController bindMigrationGenericErrorController(MigrationGenericErrorController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(StackTraceController.class)
|
||||
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Throwable> errorCause) {
|
||||
return new StackTraceController(errorCause.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
package org.cryptomator.ui.migration;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.cryptofs.migration.Migrators;
|
||||
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
|
||||
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -36,44 +32,54 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@MigrationScoped
|
||||
public class MigrationRunController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MigrationRunController.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
private static final Duration MIGRATION_PROGRESS_UPDATE_INTERVAL = Duration.millis(25);
|
||||
private static final long MIGRATION_PROGRESS_UPDATE_MILLIS = 50;
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
|
||||
private final ObjectProperty<Throwable> errorCause;
|
||||
private final Lazy<Scene> startScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final ObjectBinding<ContentDisplay> migrateButtonContentDisplay;
|
||||
private final Lazy<Scene> capabilityErrorScene;
|
||||
private final Lazy<Scene> genericErrorScene;
|
||||
private final BooleanProperty migrationButtonDisabled;
|
||||
private final DoubleProperty migrationProgress;
|
||||
private final ScheduledService<Double> migrationProgressObservationService;
|
||||
private volatile double volatileMigrationProgress = -1.0;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
|
||||
@Inject
|
||||
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene) {
|
||||
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, Optional<KeychainAccess> keychainAccess, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @Named("genericErrorCause") ObjectProperty<Throwable> errorCause, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_GENERIC_ERROR) Lazy<Scene> genericErrorScene) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.scheduler = scheduler;
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.missingCapability = missingCapability;
|
||||
this.errorCause = errorCause;
|
||||
this.startScene = startScene;
|
||||
this.successScene = successScene;
|
||||
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
|
||||
this.capabilityErrorScene = capabilityErrorScene;
|
||||
this.genericErrorScene = genericErrorScene;
|
||||
this.migrationButtonDisabled = new SimpleBooleanProperty();
|
||||
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
|
||||
this.migrationProgressObservationService = new MigrationProgressObservationService();
|
||||
migrationProgressObservationService.setExecutor(executor);
|
||||
migrationProgressObservationService.setPeriod(MIGRATION_PROGRESS_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -93,36 +99,42 @@ public class MigrationRunController implements FxController {
|
||||
LOG.info("Migrating vault {}", vault.getPath());
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
migrationProgressObservationService.start();
|
||||
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
|
||||
Platform.runLater(() -> {
|
||||
migrationProgress.set(volatileMigrationProgress);
|
||||
});
|
||||
}, 0, MIGRATION_PROGRESS_UPDATE_MILLIS, TimeUnit.MILLISECONDS);
|
||||
Tasks.create(() -> {
|
||||
Migrators migrators = Migrators.get();
|
||||
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged);
|
||||
return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME);
|
||||
}).onSuccess(needsAnotherMigration -> {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
|
||||
if (needsAnotherMigration) {
|
||||
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayableName());
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
} else {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
|
||||
vault.setState(VaultState.LOCKED);
|
||||
passwordField.swipe();
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
shakeWindow();
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
}).onError(NoApplicableMigratorException.class, e -> {
|
||||
LOG.error("Can not migrate vault.", e);
|
||||
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
|
||||
LOG.error("Underlying file system not supported.", e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
// TODO show specific error screen
|
||||
missingCapability.set(e.getMissingCapability());
|
||||
window.setScene(capabilityErrorScene.get());
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Migration failed for technical reasons.", e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
// TODO show generic error screen
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
errorCause.set(e);
|
||||
window.setScene(genericErrorScene.get());
|
||||
}).andFinally(() -> {
|
||||
migrationProgressObservationService.cancel();
|
||||
migrationProgressObservationService.reset();
|
||||
progressSyncTask.cancel(true);
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
@@ -161,54 +173,6 @@ public class MigrationRunController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
// Sets migrationProgress to volatileMigrationProgress at its configured interval
|
||||
private class MigrationProgressObservationService extends ScheduledService<Double> {
|
||||
|
||||
@Override
|
||||
protected Task<Double> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Double call() {
|
||||
return volatileMigrationProgress;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
assert getValue() != null;
|
||||
migrationProgress.set(getValue());
|
||||
super.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
||||
private void shakeWindow() {
|
||||
WritableValue<Double> writableWindowX = new WritableValue<>() {
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return window.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double value) {
|
||||
window.setX(value);
|
||||
}
|
||||
};
|
||||
Timeline timeline = new Timeline( //
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
|
||||
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
|
||||
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
|
||||
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
|
||||
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
|
||||
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
|
||||
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
|
||||
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
|
||||
@@ -18,8 +18,8 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(includes = {AutoStartModule.class})
|
||||
@@ -41,11 +41,11 @@ abstract class PreferencesModule {
|
||||
@Provides
|
||||
@PreferencesWindow
|
||||
@PreferencesScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("preferences.title"));
|
||||
stage.setResizable(false);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -37,12 +37,12 @@ abstract class QuitModule {
|
||||
@Provides
|
||||
@QuitWindow
|
||||
@QuitScoped
|
||||
static Stage provideStage(@Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setMinWidth(300);
|
||||
stage.setMinHeight(100);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AutoCompleter {
|
||||
|
||||
private final List<String> dictionary;
|
||||
|
||||
public AutoCompleter(Collection<String> dictionary) {
|
||||
this.dictionary = unmodifiableSortedRandomAccessList(dictionary);
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> List<T> unmodifiableSortedRandomAccessList(Collection<T> items) {
|
||||
List<T> result = new ArrayList<>(items);
|
||||
Collections.sort(result);
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
public Optional<String> autocomplete(String prefix) {
|
||||
if (Strings.isNullOrEmpty(prefix)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
int potentialMatchIdx = findIndexOfLexicographicallyPreceeding(0, dictionary.size(), prefix);
|
||||
if (potentialMatchIdx < dictionary.size()) {
|
||||
String potentialMatch = dictionary.get(potentialMatchIdx);
|
||||
return potentialMatch.startsWith(prefix) ? Optional.of(potentialMatch) : Optional.empty();
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first word in {@link #dictionary} that starts with a given prefix.
|
||||
*
|
||||
* This method performs an "unsuccessful" binary search (it doesn't return when encountering an exact match).
|
||||
* Instead it continues searching in the left half (which includes the exact match) until only one element is left.
|
||||
*
|
||||
* If the dictionary doesn't contain a word "left" of the given prefix, this method returns an invalid index, though.
|
||||
*
|
||||
* @param begin Index of first element (inclusive)
|
||||
* @param end Index of last element (exclusive)
|
||||
* @param prefix
|
||||
* @return index between [0, dictLen], i.e. index can exceed the upper bounds of {@link #dictionary}.
|
||||
*/
|
||||
private int findIndexOfLexicographicallyPreceeding(int begin, int end, String prefix) {
|
||||
if (begin >= end) {
|
||||
return begin; // this is usually where a binary search ends "unsuccessful"
|
||||
}
|
||||
|
||||
int mid = (begin + end) / 2;
|
||||
String word = dictionary.get(mid);
|
||||
if (prefix.compareTo(word) <= 0) { // prefix preceeds or matches word
|
||||
// proceed in left half
|
||||
assert mid < end;
|
||||
return findIndexOfLexicographicallyPreceeding(0, mid, prefix);
|
||||
} else {
|
||||
// proceed in right half
|
||||
assert mid >= begin;
|
||||
return findIndexOfLexicographicallyPreceeding(mid + 1, end, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,11 +21,21 @@ public interface RecoveryKeyComponent {
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
|
||||
Lazy<Scene> scene();
|
||||
Lazy<Scene> creationScene();
|
||||
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
||||
Lazy<Scene> recoverScene();
|
||||
|
||||
default void showRecoveryKeyCreationWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.setScene(creationScene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
default void showRecoveryKeyRecoverWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(recoverScene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -48,19 +42,26 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void createRecoveryKey() {
|
||||
Tasks.create(() -> {
|
||||
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
|
||||
}).onSuccess(result -> {
|
||||
recoveryKeyProperty.set(result);
|
||||
Task<String> task = new RecoveryKeyCreationTask();
|
||||
task.setOnScheduled(event -> {
|
||||
LOG.debug("Creating recovery key for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
String recoveryKey = task.getValue();
|
||||
recoveryKeyProperty.set(recoveryKey);
|
||||
window.setScene(successScene.get());
|
||||
}).onError(IOException.class, e -> {
|
||||
LOG.error("Creation of recovery key failed.", e);
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
shakeWindow();
|
||||
}).runOnce(executor);
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
if (task.getException() instanceof InvalidPassphraseException) {
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
} else {
|
||||
LOG.error("Creation of recovery key failed.", task.getException());
|
||||
}
|
||||
});
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -68,31 +69,13 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
private class RecoveryKeyCreationTask extends Task<String> {
|
||||
|
||||
private void shakeWindow() {
|
||||
WritableValue<Double> writableWindowX = new WritableValue<>() {
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return window.getX();
|
||||
}
|
||||
@Override
|
||||
protected String call() throws IOException {
|
||||
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double value) {
|
||||
window.setX(value);
|
||||
}
|
||||
};
|
||||
Timeline timeline = new Timeline( //
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
|
||||
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
|
||||
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
|
||||
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
|
||||
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
|
||||
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
|
||||
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
|
||||
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
@@ -4,6 +4,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.print.PageLayout;
|
||||
import javafx.print.Printer;
|
||||
import javafx.print.PrinterJob;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.text.Font;
|
||||
@@ -16,6 +17,8 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class RecoveryKeyDisplayController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
|
||||
@@ -23,11 +26,14 @@ public class RecoveryKeyDisplayController implements FxController {
|
||||
private final Stage window;
|
||||
private final String vaultName;
|
||||
private final String recoveryKey;
|
||||
|
||||
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
|
||||
private final ResourceBundle localization;
|
||||
public Button copyButton;
|
||||
|
||||
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey, ResourceBundle localization) {
|
||||
this.window = window;
|
||||
this.vaultName = vaultName;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.localization = localization;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -68,6 +74,8 @@ public class RecoveryKeyDisplayController implements FxController {
|
||||
clipboardContent.putString(recoveryKey);
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
LOG.info("Recovery key copied to clipboard.");
|
||||
|
||||
copyButton.setText(localization.getString("generic.button.copied"));
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -10,11 +10,13 @@ import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@Singleton
|
||||
public class RecoveryKeyFactory {
|
||||
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
private static final byte[] PEPPER = new byte[0];
|
||||
|
||||
private final WordEncoder wordEncoder;
|
||||
|
||||
@@ -22,6 +24,10 @@ public class RecoveryKeyFactory {
|
||||
public RecoveryKeyFactory(WordEncoder wordEncoder) {
|
||||
this.wordEncoder = wordEncoder;
|
||||
}
|
||||
|
||||
public Collection<String> getDictionary() {
|
||||
return wordEncoder.getWords();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vaultPath Path to the storage location of a vault
|
||||
@@ -32,7 +38,7 @@ public class RecoveryKeyFactory {
|
||||
* @apiNote This is a long-running operation and should be invoked in a background thread
|
||||
*/
|
||||
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException {
|
||||
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, new byte[0], password);
|
||||
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, PEPPER, password);
|
||||
try {
|
||||
return createRecoveryKey(rawKey);
|
||||
} finally {
|
||||
@@ -53,26 +59,53 @@ public class RecoveryKeyFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a completely new masterkey using a recovery key.
|
||||
* @param vaultPath Path to the storage location of a vault
|
||||
* @param recoveryKey A recovery key for this vault
|
||||
* @param newPassword The new password used to encrypt the keys
|
||||
* @throws IOException If the masterkey file could not be written
|
||||
* @throws IllegalArgumentException If the recoveryKey is invalid
|
||||
* @apiNote This is a long-running operation and should be invoked in a background thread
|
||||
*/
|
||||
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
|
||||
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
|
||||
try {
|
||||
CryptoFileSystemProvider.restoreRawKey(vaultPath, MASTERKEY_FILENAME, rawKey, PEPPER, newPassword);
|
||||
} finally {
|
||||
Arrays.fill(rawKey, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a String is a syntactically correct recovery key with a valid checksum
|
||||
* @param recoveryKey A word sequence which might be a recovery key
|
||||
* @return <code>true</code> if this seems to be a legitimate recovery key
|
||||
*/
|
||||
public boolean validateRecoveryKey(String recoveryKey) {
|
||||
final byte[] paddedKey;
|
||||
try {
|
||||
paddedKey = wordEncoder.decode(recoveryKey);
|
||||
byte[] key = decodeRecoveryKey(recoveryKey);
|
||||
Arrays.fill(key, (byte) 0x00);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
if (paddedKey.length != 66) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private byte[] decodeRecoveryKey(String recoveryKey) throws IllegalArgumentException {
|
||||
byte[] paddedKey = new byte[0];
|
||||
try {
|
||||
paddedKey = wordEncoder.decode(recoveryKey);
|
||||
Preconditions.checkArgument(paddedKey.length == 66, "Recovery key doesn't consist of 66 bytes.");
|
||||
byte[] rawKey = Arrays.copyOf(paddedKey, 64);
|
||||
byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66);
|
||||
byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes();
|
||||
byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2);
|
||||
Preconditions.checkArgument(Arrays.equals(expectedCrc16, actualCrc16), "Recovery key has invalid CRC.");
|
||||
return rawKey;
|
||||
} finally {
|
||||
Arrays.fill(paddedKey, (byte) 0x00);
|
||||
}
|
||||
byte[] rawKey = Arrays.copyOf(paddedKey, 64);
|
||||
byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66);
|
||||
byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes();
|
||||
byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2);
|
||||
return Arrays.equals(expectedCrc16, actualCrc16);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
@@ -17,11 +19,13 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -37,13 +41,13 @@ abstract class RecoveryKeyModule {
|
||||
@Provides
|
||||
@RecoveryKeyWindow
|
||||
@RecoveryKeyScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("keyRecoveryOwner") Stage owner) {
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("keyRecoveryOwner") Stage owner) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("recoveryKey.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -53,7 +57,15 @@ abstract class RecoveryKeyModule {
|
||||
static StringProperty provideRecoveryKeyProperty() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@RecoveryKeyScoped
|
||||
@Named("newPassword")
|
||||
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||
return new SimpleObjectProperty<>("");
|
||||
}
|
||||
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@@ -70,6 +82,20 @@ abstract class RecoveryKeyModule {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyRecoverScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_recover.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyResetPasswordScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_reset_password.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@@ -80,13 +106,30 @@ abstract class RecoveryKeyModule {
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyDisplayController.class)
|
||||
static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
|
||||
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get());
|
||||
static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, ResourceBundle localization) {
|
||||
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get(), localization);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyRecoverController.class)
|
||||
abstract FxController provideRecoveryKeyRecoverController(RecoveryKeyRecoverController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeySuccessController.class)
|
||||
abstract FxController bindRecoveryKeySuccessController(RecoveryKeySuccessController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyResetPasswordController.class)
|
||||
abstract FxController bindRecoveryKeyResetPasswordController(RecoveryKeyResetPasswordController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
|
||||
return new NewPasswordController(resourceBundle, strengthRater, password);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyRecoverController implements FxController {
|
||||
|
||||
private final static CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final StringProperty recoveryKey;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final BooleanBinding validRecoveryKey;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final AutoCompleter autoCompleter;
|
||||
|
||||
public TextArea textarea;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.validRecoveryKey = Bindings.createBooleanBinding(this::isValidRecoveryKey, recoveryKey);
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
recoveryKey.bind(textarea.textProperty());
|
||||
}
|
||||
|
||||
private TextFormatter.Change filterTextChange(TextFormatter.Change change) {
|
||||
if (Strings.isNullOrEmpty(change.getText())) {
|
||||
// pass-through caret/selection changes that don't affect the text
|
||||
return change;
|
||||
}
|
||||
if (!ALLOWED_CHARS.matchesAllOf(change.getText())) {
|
||||
return null; // reject change
|
||||
}
|
||||
|
||||
String text = change.getControlNewText();
|
||||
int caretPos = change.getCaretPosition();
|
||||
if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word?
|
||||
int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0);
|
||||
String currentWord = text.substring(beginOfWord, caretPos);
|
||||
Optional<String> suggestion = autoCompleter.autocomplete(currentWord);
|
||||
if (suggestion.isPresent()) {
|
||||
String completion = suggestion.get().substring(currentWord.length());
|
||||
change.setText(change.getText() + completion);
|
||||
change.setAnchor(caretPos + completion.length());
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void onKeyPressed(KeyEvent keyEvent) {
|
||||
if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) {
|
||||
// apply autocompletion:
|
||||
int pos = textarea.getAnchor();
|
||||
textarea.insertText(pos, " ");
|
||||
textarea.positionCaret(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void recover() {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public BooleanBinding validRecoveryKeyProperty() {
|
||||
return validRecoveryKey;
|
||||
}
|
||||
|
||||
public boolean isValidRecoveryKey() {
|
||||
return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get());
|
||||
}
|
||||
|
||||
public TextFormatter getRecoveryKeyTextFormatter() {
|
||||
return new TextFormatter<>(this::filterTextChange);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyResetPasswordController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final ExecutorService executor;
|
||||
private final StringProperty recoveryKey;
|
||||
private final ObjectProperty<CharSequence> newPassword;
|
||||
private final Lazy<Scene> recoverScene;
|
||||
private final BooleanBinding invalidNewPassword;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @Named("newPassword")ObjectProperty<CharSequence> newPassword, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.executor = executor;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.newPassword = newPassword;
|
||||
this.recoverScene = recoverScene;
|
||||
this.invalidNewPassword = Bindings.createBooleanBinding(this::isInvalidNewPassword, newPassword);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(recoverScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void done() {
|
||||
Task<Void> task = new ResetPasswordTask();
|
||||
task.setOnScheduled(event -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
// TODO show success screen
|
||||
window.close();
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
// TODO show generic error screen
|
||||
LOG.error("Creation of recovery key failed.", task.getException());
|
||||
});
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
private class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
protected Void call() throws IOException, IllegalArgumentException {
|
||||
recoveryKeyFactory.resetPasswordWithRecoveryKey(vault.getPath(), recoveryKey.get(), newPassword.get());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public BooleanBinding invalidNewPasswordProperty() {
|
||||
return invalidNewPassword;
|
||||
}
|
||||
|
||||
public boolean isInvalidNewPassword() {
|
||||
return newPassword.get() == null || newPassword.get().length() == 0;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -11,6 +12,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -31,6 +33,10 @@ class WordEncoder {
|
||||
this(DEFAULT_WORD_FILE);
|
||||
}
|
||||
|
||||
public List<String> getWords() {
|
||||
return words;
|
||||
}
|
||||
|
||||
public WordEncoder(String wordFile) {
|
||||
try (InputStream in = getClass().getResourceAsStream(wordFile); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII.newDecoder()); //
|
||||
@@ -78,7 +84,7 @@ class WordEncoder {
|
||||
* @throws IllegalArgumentException If the encoded string doesn't consist of a multiple of two words or one of the words is unknown to this encoder.
|
||||
*/
|
||||
public byte[] decode(String encoded) {
|
||||
List<String> splitted = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(encoded);
|
||||
List<String> splitted = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(Strings.nullToEmpty(encoded));
|
||||
Preconditions.checkArgument(splitted.size() % 2 == 0, "%s needs to be a multiple of two words", encoded);
|
||||
byte[] result = new byte[splitted.size() / 2 * 3];
|
||||
for (int i = 0; i < splitted.size(); i+=2) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -38,13 +38,13 @@ abstract class RemoveVaultModule {
|
||||
@Provides
|
||||
@RemoveVaultWindow
|
||||
@RemoveVaultScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("removeVault.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,57 +3,37 @@ package org.cryptomator.ui.traymenu;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
import org.cryptomator.ui.launcher.AppLifecycleListener;
|
||||
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Menu;
|
||||
import java.awt.MenuItem;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.EnumSet;
|
||||
import java.util.EventObject;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@TrayMenuScoped
|
||||
class TrayMenuController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuController.class);
|
||||
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final AppLifecycleListener appLifecycle;
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final PopupMenu menu;
|
||||
private final AtomicBoolean allowSuddenTermination;
|
||||
|
||||
@Inject
|
||||
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
|
||||
TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> vaults) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.appLifecycle = appLifecycle;
|
||||
this.fxApplicationStarter = fxApplicationStarter;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.vaults = vaults;
|
||||
this.menu = new PopupMenu();
|
||||
this.allowSuddenTermination = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
public PopupMenu getMenu() {
|
||||
@@ -62,40 +42,12 @@ class TrayMenuController {
|
||||
|
||||
public void initTrayMenu() {
|
||||
vaults.addListener(this::vaultListChanged);
|
||||
|
||||
rebuildMenu();
|
||||
|
||||
// register preferences shortcut
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
|
||||
Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
|
||||
}
|
||||
|
||||
// register quit handler
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
||||
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
||||
}
|
||||
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
|
||||
|
||||
// allow sudden termination
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||
Desktop.getDesktop().enableSuddenTermination();
|
||||
}
|
||||
}
|
||||
|
||||
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
rebuildMenu();
|
||||
boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
|
||||
boolean suddenTerminationChanged = allowSuddenTermination.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
|
||||
if (suddenTerminationChanged && Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||
if (allVaultsAllowTermination) {
|
||||
Desktop.getDesktop().enableSuddenTermination();
|
||||
LOG.debug("sudden termination enabled");
|
||||
} else {
|
||||
Desktop.getDesktop().disableSuddenTermination();
|
||||
LOG.debug("sudden termination disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildMenu() {
|
||||
@@ -174,37 +126,8 @@ class TrayMenuController {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
}
|
||||
|
||||
private void handleQuitRequest(EventObject e, QuitResponse response) {
|
||||
if (allowSuddenTermination.get()) {
|
||||
response.performQuit(); // really?
|
||||
} else {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response));
|
||||
}
|
||||
}
|
||||
|
||||
private void quitApplication(EventObject actionEvent) {
|
||||
handleQuitRequest(actionEvent, new QuitResponse() {
|
||||
@Override
|
||||
public void performQuit() {
|
||||
shutdownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelQuit() {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
appLifecycle.quit();
|
||||
}
|
||||
|
||||
private void forceUnmountRemainingVaults() {
|
||||
for (Vault vault : vaults) {
|
||||
if (vault.isUnlocked()) {
|
||||
try {
|
||||
vault.lock(true);
|
||||
} catch (Volume.VolumeException e) {
|
||||
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -50,22 +46,24 @@ public class UnlockController implements FxController {
|
||||
private final ExecutorService executor;
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final VaultService vaultService;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> invalidMountPointScene;
|
||||
private final Lazy<Scene> genericErrorScene;
|
||||
private final ObjectProperty<Exception> genericErrorCause;
|
||||
private final ObjectProperty<Throwable> genericErrorCause;
|
||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||
private final BooleanProperty unlockButtonDisabled;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public CheckBox savePassword;
|
||||
|
||||
@Inject
|
||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Exception> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
|
||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Throwable> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.vaultService = vaultService;
|
||||
this.successScene = successScene;
|
||||
this.invalidMountPointScene = invalidMountPointScene;
|
||||
this.genericErrorScene = genericErrorScene;
|
||||
@@ -93,36 +91,36 @@ public class UnlockController implements FxController {
|
||||
public void unlock() {
|
||||
LOG.trace("UnlockController.unlock()");
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
Tasks.create(() -> {
|
||||
vault.unlock(password);
|
||||
|
||||
Task<Vault> task = vaultService.createUnlockTask(vault, password);
|
||||
task.setOnSucceeded(event -> {
|
||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
try {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||
}
|
||||
}
|
||||
}).onSuccess(() -> {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
passwordField.swipe();
|
||||
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayableName());
|
||||
window.setScene(successScene.get());
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
shakeWindow();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
genericErrorCause.set(e);
|
||||
window.setScene(genericErrorScene.get());
|
||||
}).andFinally(() -> {
|
||||
if (!vault.isUnlocked()) {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
if (task.getException() instanceof InvalidPassphraseException) {
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
} else if (task.getException() instanceof NotDirectoryException
|
||||
|| task.getException() instanceof DirectoryNotEmptyException) {
|
||||
LOG.error("Unlock failed. Mount point not an empty directory: {}", task.getException().getMessage());
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
} else {
|
||||
LOG.error("Unlock failed for technical reasons.", task.getException());
|
||||
genericErrorCause.set(task.getException());
|
||||
window.setScene(genericErrorScene.get());
|
||||
}
|
||||
}).runOnce(executor);
|
||||
});
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
/* Save Password */
|
||||
@@ -167,33 +165,6 @@ public class UnlockController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
||||
private void shakeWindow() {
|
||||
WritableValue<Double> writableWindowX = new WritableValue<>() {
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return window.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double value) {
|
||||
window.setX(value);
|
||||
}
|
||||
};
|
||||
Timeline timeline = new Timeline( //
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
|
||||
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
|
||||
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
|
||||
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
|
||||
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
|
||||
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
|
||||
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
|
||||
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {ForgetPasswordComponent.class})
|
||||
@@ -38,19 +38,19 @@ abstract class UnlockModule {
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("unlock.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("genericErrorCause")
|
||||
@UnlockScoped
|
||||
static ObjectProperty<Exception> provideGenericErrorCause() {
|
||||
static ObjectProperty<Throwable> provideGenericErrorCause() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ abstract class UnlockModule {
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(StackTraceController.class)
|
||||
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Exception> errorCause) {
|
||||
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Throwable> errorCause) {
|
||||
return new StackTraceController(errorCause.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -13,26 +11,15 @@ import javax.inject.Inject;
|
||||
public class GeneralVaultOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||
public CheckBox unlockOnStartupCheckbox;
|
||||
|
||||
@Inject
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.changePasswordWindow = changePasswordWindow;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void changePassword() {
|
||||
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
|
||||
public void initialize() {
|
||||
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@VaultOptionsScoped
|
||||
public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||
|
||||
@Inject
|
||||
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.changePasswordWindow = changePasswordWindow;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void changePassword() {
|
||||
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoverVaultDialogue() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyRecoverWindow();
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
|
||||
@@ -38,13 +38,15 @@ abstract class VaultOptionsModule {
|
||||
@Provides
|
||||
@VaultOptionsWindow
|
||||
@VaultOptionsScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(@MainWindow Stage owner, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(vault.getDisplayableName());
|
||||
stage.setResizable(false);
|
||||
stage.setResizable(true);
|
||||
stage.setMinWidth(400);
|
||||
stage.setMinHeight(300);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -72,4 +74,9 @@ abstract class VaultOptionsModule {
|
||||
@FxControllerKey(MountOptionsController.class)
|
||||
abstract FxController bindMountOptionsController(MountOptionsController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(MasterkeyOptionsController.class)
|
||||
abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller);
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@@ -34,12 +34,12 @@ abstract class WrongFileAlertModule {
|
||||
@Provides
|
||||
@WrongFileAlertWindow
|
||||
@WrongFileAlertScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("wrongFileAlert.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
BIN
main/ui/src/main/resources/bot.png
Normal file
BIN
main/ui/src/main/resources/bot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
BIN
main/ui/src/main/resources/bot@2x.png
Normal file
BIN
main/ui/src/main/resources/bot@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('dosis-bold.ttf');
|
||||
src: url('quicksand-bold.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -27,39 +27,37 @@
|
||||
******************************************************************************/
|
||||
|
||||
.root {
|
||||
GREEN_0: #373B30;
|
||||
GREEN_1: #384D14;
|
||||
GREEN_2: #476611;
|
||||
GREEN_3: #598016;
|
||||
GREEN_4: #699917;
|
||||
GREEN_5: #79B01A;
|
||||
GREEN_6: #91C734;
|
||||
GREEN_7: #B9E070;
|
||||
GREEN_8: #D7E7BA;
|
||||
GREEN_9: #F3F5F0;
|
||||
PRIMARY_D2: #2D4D2E;
|
||||
PRIMARY_D1: #407F41;
|
||||
PRIMARY: #49B04A;
|
||||
PRIMARY_L1: #66CC68;
|
||||
PRIMARY_L2: #EBF5EB;
|
||||
|
||||
GRAY_0: #222222;
|
||||
GRAY_1: #3B3B3B;
|
||||
GRAY_2: #515151;
|
||||
GRAY_3: #626262;
|
||||
GRAY_4: #7E7E7E;
|
||||
GRAY_5: #9E9E9E;
|
||||
GRAY_6: #B1B1B1;
|
||||
GRAY_7: #CFCFCF;
|
||||
GRAY_8: #E1E1E1;
|
||||
GRAY_9: #F7F7F7;
|
||||
SECONDARY: #008A7B;
|
||||
|
||||
GRAY_0: #1F2122;
|
||||
GRAY_1: #35393B;
|
||||
GRAY_2: #494E51;
|
||||
GRAY_3: #585E62;
|
||||
GRAY_4: #71797E;
|
||||
GRAY_5: #8E989E;
|
||||
GRAY_6: #9FAAB1;
|
||||
GRAY_7: #BEC9CF;
|
||||
GRAY_8: #D3DCE1;
|
||||
GRAY_9: #EDF3F7;
|
||||
|
||||
RED_5: #E74C3C;
|
||||
ORANGE_5: #E67E22;
|
||||
YELLOW_5: #F1C40F;
|
||||
|
||||
PRIMARY_BG: GREEN_3;
|
||||
SECONDARY_BG: GRAY_3;
|
||||
MAIN_BG: GRAY_1;
|
||||
TEXT_FILL: GRAY_9;
|
||||
TEXT_FILL_PRIMARY: GREEN_5;
|
||||
TEXT_FILL_SECONDARY: GRAY_5;
|
||||
TEXT_FILL_WHITE: white;
|
||||
TEXT_FILL_HIGHLIGHTED: PRIMARY;
|
||||
TEXT_FILL_MUTED: GRAY_5;
|
||||
|
||||
TITLE_BG: linear-gradient(to bottom, GRAY_2, GRAY_1);
|
||||
TITLE_TEXT_FILL: PRIMARY;
|
||||
|
||||
CONTROL_BORDER_NORMAL: GRAY_3;
|
||||
CONTROL_BORDER_FOCUSED: GRAY_5;
|
||||
CONTROL_BORDER_DISABLED: GRAY_2;
|
||||
@@ -67,27 +65,20 @@
|
||||
CONTROL_BG_HOVER: GRAY_1;
|
||||
CONTROL_BG_ARMED: GRAY_2;
|
||||
CONTROL_BG_DISABLED: GRAY_1;
|
||||
CONTROL_PRIMARY_BORDER_NORMAL: GREEN_5;
|
||||
CONTROL_PRIMARY_BORDER_FOCUSED: GREEN_7;
|
||||
CONTROL_PRIMARY_BORDER_DISABLED: GREEN_3;
|
||||
CONTROL_PRIMARY_BG_NORMAL: GREEN_3;
|
||||
CONTROL_PRIMARY_BG_ARMED: GREEN_4;
|
||||
CONTROL_PRIMARY_BG_DISABLED: GREEN_2;
|
||||
CONTROL_PRIMARY_LIGHT_BG_NORMAL: GREEN_0;
|
||||
CONTROL_WHITE_BG_ARMED: GRAY_8;
|
||||
CONTROL_BG_SELECTED: GRAY_1;
|
||||
|
||||
CONTROL_PRIMARY_BORDER_NORMAL: PRIMARY;
|
||||
CONTROL_PRIMARY_BORDER_ARMED: PRIMARY_L1;
|
||||
CONTROL_PRIMARY_BORDER_FOCUSED: SECONDARY;
|
||||
CONTROL_PRIMARY_BG_NORMAL: PRIMARY;
|
||||
CONTROL_PRIMARY_BG_ARMED: PRIMARY_L1;
|
||||
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_4;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
|
||||
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_7;
|
||||
PROGRESS_INDICATOR_END: GRAY_5;
|
||||
PROGRESS_BAR_BG: GRAY_2;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_3;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2: YELLOW_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3: GREEN_6;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4: GREEN_5;
|
||||
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
@@ -105,7 +96,7 @@
|
||||
}
|
||||
|
||||
.label-secondary {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.label-large {
|
||||
@@ -136,11 +127,11 @@
|
||||
}
|
||||
|
||||
.glyph-icon-primary {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.glyph-icon-secondary {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
.glyph-icon-muted {
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.glyph-icon-white {
|
||||
@@ -158,15 +149,16 @@
|
||||
******************************************************************************/
|
||||
|
||||
.main-window .title {
|
||||
-fx-background-color: PRIMARY_BG;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, TITLE_BG;
|
||||
-fx-background-insets: 0, 0 0 1px 0;
|
||||
}
|
||||
|
||||
.main-window .title .label {
|
||||
-fx-font-family: 'Dosis';
|
||||
-fx-font-size: 21px;
|
||||
-fx-font-family: 'Quicksand';
|
||||
-fx-font-size: 16px;
|
||||
-fx-font-style: normal;
|
||||
-fx-font-weight: 700;
|
||||
-fx-text-fill: white;
|
||||
-fx-text-fill: TITLE_TEXT_FILL;
|
||||
}
|
||||
|
||||
.main-window .title .button {
|
||||
@@ -181,24 +173,23 @@
|
||||
}
|
||||
|
||||
.main-window .title .button:armed .glyph-icon {
|
||||
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
||||
-fx-fill: GRAY_8;
|
||||
}
|
||||
|
||||
.main-window .update-indicator {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px, 4px;
|
||||
-fx-translate-x: -1px;
|
||||
-fx-translate-y: 1px;
|
||||
-fx-background-color: white, RED_5;
|
||||
-fx-background-insets: 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px;
|
||||
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 0);
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator {
|
||||
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
|
||||
-fx-background-color: SECONDARY;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
|
||||
@@ -225,24 +216,24 @@
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -271,16 +262,16 @@
|
||||
}
|
||||
|
||||
.list-view:focused .list-cell:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
.list-cell:selected {
|
||||
-fx-background-color: CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.list-cell .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.list-cell .header-label {
|
||||
@@ -289,16 +280,16 @@
|
||||
}
|
||||
|
||||
.list-cell .detail-label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.list-cell:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.list-cell:selected .header-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
|
||||
}
|
||||
|
||||
.list-cell.drop-above {
|
||||
@@ -379,13 +370,13 @@
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-background-color: PRIMARY_BG;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-background-color: SECONDARY_BG;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: SECONDARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -395,27 +386,27 @@
|
||||
******************************************************************************/
|
||||
|
||||
.password-strength-indicator .segment {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-0 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0;
|
||||
-fx-background-color: RED_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-1 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1;
|
||||
-fx-background-color: ORANGE_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-2 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2;
|
||||
-fx-background-color: YELLOW_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-3 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3;
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-4 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4;
|
||||
-fx-background-color: PRIMARY_D1;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -442,8 +433,8 @@
|
||||
.text-input {
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-highlight-fill: PRIMARY;
|
||||
-fx-prompt-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -455,7 +446,7 @@
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
@@ -487,7 +478,7 @@
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
@@ -499,8 +490,8 @@
|
||||
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-highlight-fill: PRIMARY;
|
||||
-fx-prompt-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
@@ -529,7 +520,7 @@
|
||||
}
|
||||
|
||||
.button:default {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_NORMAL;
|
||||
}
|
||||
|
||||
@@ -538,25 +529,25 @@
|
||||
}
|
||||
|
||||
.button:default:armed {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_ARMED;
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_ARMED, CONTROL_PRIMARY_BG_ARMED;
|
||||
}
|
||||
|
||||
.button:disabled,
|
||||
.button:default:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
.button:disabled .glyph-icon {
|
||||
-fx-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.button:default .glyph-icon {
|
||||
-fx-fill: TEXT_FILL_WHITE;
|
||||
-fx-fill: white;
|
||||
}
|
||||
|
||||
.button:default .label {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
.button-large {
|
||||
@@ -577,7 +568,7 @@
|
||||
}
|
||||
|
||||
.hyperlink.hyperlink-secondary {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.hyperlink-hover-icon {
|
||||
@@ -601,7 +592,7 @@
|
||||
}
|
||||
|
||||
.check-box:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.check-box > .box {
|
||||
@@ -694,7 +685,7 @@
|
||||
}
|
||||
|
||||
.choice-box:disabled > .label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.choice-box > .open-button {
|
||||
@@ -709,7 +700,7 @@
|
||||
}
|
||||
|
||||
.choice-box:disabled > .open-button > .arrow {
|
||||
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: transparent, TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.choice-box .context-menu {
|
||||
@@ -758,7 +749,7 @@
|
||||
}
|
||||
|
||||
.menu-item:disabled > .label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.radio-menu-item:checked > .left-container > .radio {
|
||||
|
||||
Binary file not shown.
BIN
main/ui/src/main/resources/css/fontawesome5-free-solid.otf
Normal file
BIN
main/ui/src/main/resources/css/fontawesome5-free-solid.otf
Normal file
Binary file not shown.
Binary file not shown.
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('dosis-bold.ttf');
|
||||
src: url('quicksand-bold.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -27,16 +27,13 @@
|
||||
******************************************************************************/
|
||||
|
||||
.root {
|
||||
GREEN_0: #373B30;
|
||||
GREEN_1: #384D14;
|
||||
GREEN_2: #476611;
|
||||
GREEN_3: #598016;
|
||||
GREEN_4: #699917;
|
||||
GREEN_5: #79B01A;
|
||||
GREEN_6: #91C734;
|
||||
GREEN_7: #B9E070;
|
||||
GREEN_8: #D7E7BA;
|
||||
GREEN_9: #F3F5F0;
|
||||
PRIMARY_D2: #2D4D2E;
|
||||
PRIMARY_D1: #407F41;
|
||||
PRIMARY: #49B04A;
|
||||
PRIMARY_L1: #66CC68;
|
||||
PRIMARY_L2: #EBF5EB;
|
||||
|
||||
SECONDARY: #008A7B;
|
||||
|
||||
GRAY_0: #222222;
|
||||
GRAY_1: #3B3B3B;
|
||||
@@ -53,13 +50,14 @@
|
||||
ORANGE_5: #E67E22;
|
||||
YELLOW_5: #F1C40F;
|
||||
|
||||
PRIMARY_BG: GREEN_5;
|
||||
SECONDARY_BG: GRAY_5;
|
||||
MAIN_BG: GRAY_9;
|
||||
TEXT_FILL: GRAY_0;
|
||||
TEXT_FILL_PRIMARY: GREEN_4;
|
||||
TEXT_FILL_SECONDARY: GRAY_4;
|
||||
TEXT_FILL_WHITE: white;
|
||||
TEXT_FILL_HIGHLIGHTED: PRIMARY;
|
||||
TEXT_FILL_MUTED: GRAY_5;
|
||||
|
||||
TITLE_BG: PRIMARY;
|
||||
TITLE_TEXT_FILL: white;
|
||||
|
||||
CONTROL_BORDER_NORMAL: GRAY_7;
|
||||
CONTROL_BORDER_FOCUSED: GRAY_5;
|
||||
CONTROL_BORDER_DISABLED: GRAY_8;
|
||||
@@ -67,27 +65,20 @@
|
||||
CONTROL_BG_HOVER: GRAY_9;
|
||||
CONTROL_BG_ARMED: GRAY_8;
|
||||
CONTROL_BG_DISABLED: GRAY_9;
|
||||
CONTROL_PRIMARY_BORDER_NORMAL: GREEN_3;
|
||||
CONTROL_PRIMARY_BORDER_FOCUSED: GREEN_1;
|
||||
CONTROL_PRIMARY_BORDER_DISABLED: GREEN_5;
|
||||
CONTROL_PRIMARY_BG_NORMAL: GREEN_5;
|
||||
CONTROL_PRIMARY_BG_ARMED: GREEN_4;
|
||||
CONTROL_PRIMARY_BG_DISABLED: GREEN_6;
|
||||
CONTROL_PRIMARY_LIGHT_BG_NORMAL: GREEN_9;
|
||||
CONTROL_WHITE_BG_ARMED: GRAY_8;
|
||||
CONTROL_BG_SELECTED: PRIMARY_L2;
|
||||
|
||||
CONTROL_PRIMARY_BORDER_NORMAL: PRIMARY_D1;
|
||||
CONTROL_PRIMARY_BORDER_ARMED: PRIMARY_D2;
|
||||
CONTROL_PRIMARY_BORDER_FOCUSED: SECONDARY;
|
||||
CONTROL_PRIMARY_BG_NORMAL: PRIMARY;
|
||||
CONTROL_PRIMARY_BG_ARMED: PRIMARY_D1;
|
||||
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_6;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
|
||||
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_2;
|
||||
PROGRESS_INDICATOR_END: GRAY_4;
|
||||
PROGRESS_BAR_BG: GRAY_8;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2: YELLOW_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3: GREEN_6;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4: GREEN_5;
|
||||
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
@@ -105,7 +96,7 @@
|
||||
}
|
||||
|
||||
.label-secondary {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.label-large {
|
||||
@@ -136,11 +127,11 @@
|
||||
}
|
||||
|
||||
.glyph-icon-primary {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.glyph-icon-secondary {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
.glyph-icon-muted {
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.glyph-icon-white {
|
||||
@@ -158,15 +149,15 @@
|
||||
******************************************************************************/
|
||||
|
||||
.main-window .title {
|
||||
-fx-background-color: PRIMARY_BG;
|
||||
-fx-background-color: TITLE_BG;
|
||||
}
|
||||
|
||||
.main-window .title .label {
|
||||
-fx-font-family: 'Dosis';
|
||||
-fx-font-size: 21px;
|
||||
-fx-font-family: 'Quicksand';
|
||||
-fx-font-size: 16px;
|
||||
-fx-font-style: normal;
|
||||
-fx-font-weight: 700;
|
||||
-fx-text-fill: white;
|
||||
-fx-text-fill: TITLE_TEXT_FILL;
|
||||
}
|
||||
|
||||
.main-window .title .button {
|
||||
@@ -181,24 +172,23 @@
|
||||
}
|
||||
|
||||
.main-window .title .button:armed .glyph-icon {
|
||||
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
||||
-fx-fill: GRAY_8;
|
||||
}
|
||||
|
||||
.main-window .update-indicator {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px, 4px;
|
||||
-fx-translate-x: -1px;
|
||||
-fx-translate-y: 1px;
|
||||
-fx-background-color: white, RED_5;
|
||||
-fx-background-insets: 1px, 2px;
|
||||
-fx-background-radius: 6px, 5px;
|
||||
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 0);
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator {
|
||||
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
|
||||
-fx-background-color: SECONDARY;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
|
||||
@@ -225,24 +215,24 @@
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -271,16 +261,16 @@
|
||||
}
|
||||
|
||||
.list-view:focused .list-cell:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
.list-cell:selected {
|
||||
-fx-background-color: CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.list-cell .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.list-cell .header-label {
|
||||
@@ -289,16 +279,16 @@
|
||||
}
|
||||
|
||||
.list-cell .detail-label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.list-cell:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
-fx-fill: PRIMARY;
|
||||
}
|
||||
|
||||
.list-cell:selected .header-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
|
||||
}
|
||||
|
||||
.list-cell.drop-above {
|
||||
@@ -379,13 +369,13 @@
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-background-color: PRIMARY_BG;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-background-color: SECONDARY_BG;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: SECONDARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -395,27 +385,27 @@
|
||||
******************************************************************************/
|
||||
|
||||
.password-strength-indicator .segment {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-0 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0;
|
||||
-fx-background-color: RED_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-1 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1;
|
||||
-fx-background-color: ORANGE_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-2 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2;
|
||||
-fx-background-color: YELLOW_5;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-3 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3;
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.password-strength-indicator.strength-4 .segment.active {
|
||||
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4;
|
||||
-fx-background-color: PRIMARY_D1;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -442,8 +432,8 @@
|
||||
.text-input {
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-highlight-fill: PRIMARY;
|
||||
-fx-prompt-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -455,7 +445,7 @@
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
@@ -487,7 +477,7 @@
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
@@ -499,8 +489,8 @@
|
||||
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-highlight-fill: PRIMARY;
|
||||
-fx-prompt-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
@@ -529,7 +519,7 @@
|
||||
}
|
||||
|
||||
.button:default {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_NORMAL;
|
||||
}
|
||||
|
||||
@@ -538,25 +528,25 @@
|
||||
}
|
||||
|
||||
.button:default:armed {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_ARMED;
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_ARMED, CONTROL_PRIMARY_BG_ARMED;
|
||||
}
|
||||
|
||||
.button:disabled,
|
||||
.button:default:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
.button:disabled .glyph-icon {
|
||||
-fx-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.button:default .glyph-icon {
|
||||
-fx-fill: TEXT_FILL_WHITE;
|
||||
-fx-fill: white;
|
||||
}
|
||||
|
||||
.button:default .label {
|
||||
-fx-text-fill: TEXT_FILL_WHITE;
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
.button-large {
|
||||
@@ -577,7 +567,7 @@
|
||||
}
|
||||
|
||||
.hyperlink.hyperlink-secondary {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.hyperlink-hover-icon {
|
||||
@@ -601,7 +591,7 @@
|
||||
}
|
||||
|
||||
.check-box:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.check-box > .box {
|
||||
@@ -694,7 +684,7 @@
|
||||
}
|
||||
|
||||
.choice-box:disabled > .label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.choice-box > .open-button {
|
||||
@@ -709,7 +699,7 @@
|
||||
}
|
||||
|
||||
.choice-box:disabled > .open-button > .arrow {
|
||||
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: transparent, TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.choice-box .context-menu {
|
||||
@@ -758,7 +748,7 @@
|
||||
}
|
||||
|
||||
.menu-item:disabled > .label {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
}
|
||||
|
||||
.radio-menu-item:checked > .left-container > .radio {
|
||||
|
||||
BIN
main/ui/src/main/resources/css/quicksand-bold.ttf
Normal file
BIN
main/ui/src/main/resources/css/quicksand-bold.ttf
Normal file
Binary file not shown.
@@ -21,7 +21,7 @@
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
|
||||
<Image url="/bot_welcome.png"/>
|
||||
<Image url="/bot.png"/>
|
||||
</ImageView>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
@@ -29,7 +29,7 @@
|
||||
<VBox alignment="CENTER" spacing="9">
|
||||
<Button styleClass="button-large" text="%addvaultwizard.welcome.newButton" onAction="#createNewVault" prefWidth="Infinity">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="SPARKLES" glyphSize="15"/>
|
||||
<FontAwesome5IconView glyph="MAGIC" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button styleClass="button-large" text="%addvaultwizard.welcome.existingButton" onAction="#chooseExistingVault" prefWidth="Infinity">
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
||||
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.changepassword.ChangePasswordController"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<HBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="titleBar"
|
||||
@@ -19,13 +19,13 @@
|
||||
<Insets bottom="6" left="12" right="12" top="6"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="Cryptomator"/>
|
||||
<Label text="CRYPTOMATOR"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
|
||||
<graphic>
|
||||
<StackPane>
|
||||
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
|
||||
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||
</StackPane>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
@@ -36,13 +36,21 @@
|
||||
<graphic>
|
||||
<StackPane>
|
||||
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
|
||||
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||
</StackPane>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.preferencesBtn.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${!controller.minimizeToSysTray}" managed="${!controller.minimizeToSysTray}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="WINDOW_MINIMIZE" glyphSize="12"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.minimizeBtn.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.migration.MigrationCapabilityErrorController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<HBox spacing="12" VBox.vgrow="ALWAYS">
|
||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||
</StackPane>
|
||||
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
||||
<Label text="%migration.error.missingFileSystemCapabilities.title" wrapText="true"/>
|
||||
<Label text="%migration.error.missingFileSystemCapabilities.description" wrapText="true"/>
|
||||
<Label text="${controller.missingCapabilityDescription}" wrapText="true"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
|
||||
<Region ButtonBar.buttonData="OTHER"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
32
main/ui/src/main/resources/fxml/migration_generic_error.fxml
Normal file
32
main/ui/src/main/resources/fxml/migration_generic_error.fxml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.migration.MigrationGenericErrorController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<fx:include source="/fxml/stacktrace.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
|
||||
<Region ButtonBar.buttonData="OTHER"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -22,7 +22,7 @@
|
||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
|
||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="USER_CROWN" glyphSize="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
|
||||
</StackPane>
|
||||
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
|
||||
</HBox>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<FontAwesome5IconView glyph="PRINT"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
|
||||
<Button fx:id="copyButton" text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="COPY"/>
|
||||
</graphic>
|
||||
|
||||
45
main/ui/src/main/resources/fxml/recoverykey_recover.fxml
Normal file
45
main/ui/src/main/resources/fxml/recoverykey_recover.fxml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyRecoverController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
|
||||
|
||||
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
|
||||
|
||||
<Label text="%recoveryKey.recover.validKey" graphicTextGap="6" contentDisplay="LEFT" visible="${controller.validRecoveryKey}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="C+X">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close" />
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validRecoveryKey}" />
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<fx:include source="/fxml/new_password.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+I">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back" />
|
||||
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#done" disable="${controller.invalidNewPassword}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -39,7 +39,7 @@
|
||||
</HBox>
|
||||
<Hyperlink styleClass="hyperlink-secondary,hyperlink-hover-icon" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" onAction="#revealStorageLocation">
|
||||
<graphic>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-secondary" glyph="EYE"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-muted" glyph="EYE"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
@@ -25,12 +24,11 @@
|
||||
</ContextMenu>
|
||||
</contextMenu>
|
||||
</ListView>
|
||||
<AnchorPane visible="${controller.emptyVaultList}">
|
||||
<HBox AnchorPane.leftAnchor="10" AnchorPane.rightAnchor="10" AnchorPane.bottomAnchor="100" alignment="BOTTOM_LEFT">
|
||||
<Label textAlignment="CENTER" text="%main.vaultlist.emptyList.onboardingInstruction" wrapText="true"/>
|
||||
</HBox>
|
||||
<Arc styleClass="onboarding-overlay-arc" AnchorPane.bottomAnchor="5" type="OPEN" centerX="-10" centerY="0" radiusY="100" radiusX="60" startAngle="0" length="-60" strokeWidth="1"/>
|
||||
</AnchorPane>
|
||||
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" wrapText="true"/>
|
||||
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
|
||||
<graphic>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="tabPane"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
|
||||
minWidth="400"
|
||||
prefWidth="400"
|
||||
tabMinWidth="60"
|
||||
tabClosingPolicy="UNAVAILABLE"
|
||||
tabDragPolicy="FIXED">
|
||||
@@ -28,5 +28,13 @@
|
||||
<fx:include source="/fxml/vault_options_mount.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="keyTab" text="%vaultOptions.masterkey">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="KEY"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="/fxml/vault_options_masterkey.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
@@ -11,7 +11,6 @@
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Button text="%vaultOptions.general.changePasswordBtn" onAction="#changePassword"/>
|
||||
<Button text="%vaultOptions.general.showRecoveryKeyBtn" onAction="#showRecoveryKey"/>
|
||||
<CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
43
main/ui/src/main/resources/fxml/vault_options_masterkey.fxml
Normal file
43
main/ui/src/main/resources/fxml/vault_options_masterkey.fxml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.MasterkeyOptionsController"
|
||||
spacing="6"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Button text="%vaultOptions.masterkey.changePasswordBtn" onAction="#changePassword" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="KEY"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<Label maxWidth="-Infinity" text="%vaultOptions.masterkey.recoveryKeyExpanation" wrapText="true"/>
|
||||
<HBox spacing="6" alignment="CENTER">
|
||||
<Button text="%vaultOptions.masterkey.showRecoveryKeyBtn" onAction="#showRecoveryKey" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="EYE"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button text="%vaultOptions.masterkey.recoverPasswordBtn" onAction="#showRecoverVaultDialogue" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="SYNC"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -8,6 +8,7 @@ generic.button.back=Back
|
||||
generic.button.cancel=Cancel
|
||||
generic.button.change=Change
|
||||
generic.button.copy=Copy
|
||||
generic.button.copied=Copied!
|
||||
generic.button.done=Done
|
||||
generic.button.next=Next
|
||||
generic.button.print=Print
|
||||
@@ -49,12 +50,12 @@ addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your da
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Yes please, better safe than sorry
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=No thanks, I will not lose my password
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
|
||||
addvault.new.readme.storageLocation.fileName=HELP.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT DIRECTORY ⚠️
|
||||
addvault.new.readme.storageLocation.2=This is your vault's storage location. {\\b DO NOT} alter any files within this directory.
|
||||
addvault.new.readme.storageLocation.3=If you want to encrypt files using Cryptomator, unlock the vault and use the provided drive.
|
||||
addvault.new.readme.storageLocation.4=If you need help, try %s.
|
||||
addvault.new.readme.accessLocation.fileName=WELCOME TO YOUR VAULT.rtf
|
||||
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ENCRYPTED VOLUME 🔐️
|
||||
addvault.new.readme.accessLocation.2=This is your vault's access location. Any files added to this volume will be encrypted by Cryptomator. To access this volume at a later point in time, simply unlock it again from within the Cryptomator application.
|
||||
addvault.new.readme.accessLocation.3=Feel free to remove this file.
|
||||
@@ -104,6 +105,11 @@ migration.run.startMigrationBtn=Migrate Vault
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Migrated "%s" successfully.\nYou can now unlock your vault.
|
||||
migration.success.unlockNow=Unlock Now
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Unsupported File System
|
||||
migration.error.missingFileSystemCapabilities.description=Migration was not started, because your vault is located on an inadequate file system.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=The file system does not support long file names.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=The file system does not support long paths.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Preferences
|
||||
@@ -135,6 +141,7 @@ preferences.donationKey.getDonationKey=Get a donation key
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Close
|
||||
main.minimizeBtn.tooltip=Minimize
|
||||
main.preferencesBtn.tooltip=Preferences
|
||||
main.donationKeyMissing.tooltip=Please consider donating
|
||||
## Drag 'n' Drop
|
||||
@@ -171,8 +178,7 @@ wrongFileAlert.information=You have tried to add a file or folder that does not
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.changePasswordBtn=Change Password
|
||||
vaultOptions.general.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Mounting
|
||||
vaultOptions.mount.readonly=Read-Only
|
||||
@@ -185,12 +191,20 @@ vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
|
||||
vaultOptions.mount.mountPoint.custom=Custom path
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Password
|
||||
vaultOptions.masterkey.changePasswordBtn=Change Password
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Recovery Key
|
||||
recoveryKey.enterPassword.prompt=Enter your password to show the recovery key for "%s":
|
||||
recoveryKey.display.message=The following recovery key can be used to restore access to "%s":
|
||||
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
|
||||
recoveryKey.recover.prompt=Enter your recovery key for "%s":
|
||||
recoveryKey.recover.validKey=This is a valid recovery key
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Enter a new password
|
||||
|
||||
@@ -43,8 +43,10 @@ addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جر
|
||||
## Existing
|
||||
addvaultwizard.existing.chooseBtn=اختر…
|
||||
## Success
|
||||
addvaultwizard.success.unlockNow=افتح الان
|
||||
|
||||
# Remove Vault
|
||||
removeVault.title=احذف الحافظة
|
||||
removeVault.confirmBtn=احذف الحافظة
|
||||
|
||||
# Change Password
|
||||
@@ -69,6 +71,7 @@ migration.start.confirm=نعم, محفظتي متزامنة بالكامل
|
||||
migration.run.startMigrationBtn=ترقية الحافظة
|
||||
## Sucess
|
||||
migration.success.unlockNow=افتح الان
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
preferences.title=تفضيلات
|
||||
@@ -86,6 +89,7 @@ preferences.updates.checkNowBtn=تحقق الان
|
||||
main.preferencesBtn.tooltip=تفضيلات
|
||||
## Drag 'n' Drop
|
||||
## Vault List
|
||||
main.vaultlist.contextMenu.remove=احذف الحافظة
|
||||
main.vaultlist.addVaultBtn=أضِف مخزنًا
|
||||
## Vault Detail
|
||||
### Locked
|
||||
@@ -104,9 +108,13 @@ main.vaultDetail.migrateButton=ترقية الحافظة
|
||||
vaultOptions.general=عام
|
||||
## Mount
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
||||
## Master Key
|
||||
|
||||
# Recovery Key
|
||||
|
||||
# New Password
|
||||
newPassword.reenterPassword=تأكيد كلمة المرور الجديدة
|
||||
newPassword.passwordsMatch=كلمات المرور متطابقة!
|
||||
newPassword.passwordsDoNotMatch=كلمات المرور غير متطابقة
|
||||
|
||||
# Quit
|
||||
|
||||
@@ -43,12 +43,9 @@ addvaultwizard.new.createVaultBtn=Vytvořit trezor
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ano prosím, jistota je jistota
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Ne, díky, neztratím své heslo
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
|
||||
addvault.new.readme.storageLocation.2=Toto je umístění vašeho trezoru. NEMĚŇTE žádné soubory uvnitř adresáře.
|
||||
addvault.new.readme.storageLocation.3=Pokud chcete zašifrovat soubory pomocí Cryptomatoru, odemkněte trezor a použijte přidělený disk.
|
||||
addvault.new.readme.storageLocation.4=Pokud potřebujete pomoc, zkuste %s.
|
||||
addvault.new.readme.accessLocation.fileName=Vítej ve svém novém trezoru.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ZAŠIFROVANÁ JEDNOTKA 🔐️
|
||||
addvault.new.readme.accessLocation.2=Toto je přístup k vašemu trezoru. Všechny soubory přidané do této jednotky budou šifrovány pomocí Cryptomatoru. Pro přístup k této jednotce kdykoliv v budoucnu ji jednoduše opět odemkněte pomocí Cryptomatoru.
|
||||
addvault.new.readme.accessLocation.3=Tento soubor můžete odstranit.
|
||||
@@ -96,6 +93,7 @@ migration.run.startMigrationBtn=Migrovat trezor
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Migrace "%s" byla úspěšná.\nNyní můžete svůj trezor odemknout.
|
||||
migration.success.unlockNow=Odemknout nyní
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
preferences.title=Nastavení
|
||||
@@ -158,8 +156,6 @@ wrongFileAlert.information=Zkoušíte přidat soubor či adresář, který zřej
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Obecné
|
||||
vaultOptions.general.changePasswordBtn=Změnit heslo
|
||||
vaultOptions.general.showRecoveryKeyBtn=Zobrazit klíč k obnově
|
||||
## Mount
|
||||
vaultOptions.mount=Připojení
|
||||
vaultOptions.mount.readonly=Pouze pro čtení
|
||||
@@ -172,6 +168,9 @@ vaultOptions.mount.mountPoint.driveLetter=Použít přiřazené písmeno
|
||||
vaultOptions.mount.mountPoint.custom=Vlastní cesta
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
|
||||
## Master Key
|
||||
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Klíč k obnově
|
||||
@@ -180,6 +179,9 @@ recoveryKey.display.message=Následující obnovovací klíč může být použi
|
||||
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
|
||||
|
||||
# New Password
|
||||
newPassword.reenterPassword=Potvrď nové heslo
|
||||
newPassword.passwordsMatch=Hesla se shodují!
|
||||
newPassword.passwordsDoNotMatch=Hesla se neshodují
|
||||
passwordStrength.messageLabel.0=velmi slabé
|
||||
passwordStrength.messageLabel.1=slabé
|
||||
passwordStrength.messageLabel.2=dostatečné
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Zurück
|
||||
generic.button.cancel=Abbrechen
|
||||
generic.button.change=Ändern
|
||||
generic.button.copy=Kopieren
|
||||
generic.button.copied=Kopiert!
|
||||
generic.button.done=Fertig
|
||||
generic.button.next=Weiter
|
||||
generic.button.print=Drucken
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Ohne dieses Passwort kannst du auf
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ TRESORDATEIEN ⚠️
|
||||
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors. Ändere {\\b KEINE} Dateien in diesem Verzeichnis.
|
||||
addvault.new.readme.storageLocation.3=Wenn du Dateien mit Cryptomator verschlüsseln möchtest, entsperre den Tresor und verwende das zur Verfügung gestellte Laufwerk.
|
||||
addvault.new.readme.storageLocation.4=Falls du Hilfe brauchst, versuche %s.
|
||||
addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZU DEINEM TRESOR.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️
|
||||
addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
|
||||
addvault.new.readme.accessLocation.3=Diese Datei kannst du löschen.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Tresor migrieren
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=„%s“ erfolgreich migriert.\nDu kannst deinen Tresor jetzt entsperren.
|
||||
migration.success.unlockNow=Jetzt entsperren
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Nicht unterstütztes Dateisystem
|
||||
migration.error.missingFileSystemCapabilities.description=Die Migration wurde nicht gestartet, da sich dein Tresor auf einem ungeeigneten Dateisystem befindet.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Das Dateisystem unterstützt keine langen Dateinamen.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Das Dateisystem unterstützt keine langen Pfadnamen.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Einstellungen
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Einen Spendenschlüssel erhalten
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Schließen
|
||||
main.minimizeBtn.tooltip=Minimieren
|
||||
main.preferencesBtn.tooltip=Einstellungen
|
||||
main.donationKeyMissing.tooltip=Bitte denke über eine Spende nach
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuz
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Allgemein
|
||||
vaultOptions.general.changePasswordBtn=Passwort ändern
|
||||
vaultOptions.general.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
|
||||
vaultOptions.general.unlockAfterStartup=Tresor beim Starten von Cryptomator entsperren
|
||||
## Mount
|
||||
vaultOptions.mount=Laufwerk
|
||||
vaultOptions.mount.readonly=Schreibgeschützt
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Zugewiesenen Laufwerksbuchstaben verwe
|
||||
vaultOptions.mount.mountPoint.custom=Eigener Pfad
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen …
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Passwort
|
||||
vaultOptions.masterkey.changePasswordBtn=Password ändern
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Bei Verlust deines Passworts ist ein Wiederherstellungsschlüssel deine einzige Möglichkeit, den Zugriff auf einen Tresor wiederherzustellen.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Passwort wiederherstellen
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Wiederherstellungsschlüssel
|
||||
recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
|
||||
recoveryKey.display.message=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
|
||||
recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
|
||||
recoveryKey.recover.prompt=Gib deinen Wiederherstellungsschlüssel für „%s“ ein:
|
||||
recoveryKey.recover.validKey=Dies ist ein gültiger Wiederherstellungsschlüssel
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Gib ein neues Passwort ein
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
## Start
|
||||
## Run
|
||||
## Sucess
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
## General
|
||||
@@ -50,6 +51,7 @@
|
||||
# Vault Options
|
||||
## General
|
||||
## Mount
|
||||
## Master Key
|
||||
|
||||
# Recovery Key
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Volver
|
||||
generic.button.cancel=Cancelar
|
||||
generic.button.change=Modificar
|
||||
generic.button.copy=Copiar
|
||||
generic.button.copied=¡Copiado!
|
||||
generic.button.done=Hecho
|
||||
generic.button.next=Continuar
|
||||
generic.button.print=Imprimir
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=No podrá acceder a sus datos sin s
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Sí, por favor
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=No, gracias
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=QUÉ ES ESTE DIRECTORIO.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARCHIVOS DE LA BÓVEDA ⚠️
|
||||
addvault.new.readme.storageLocation.2=Esta es la ubicación del almacenamiento de su bóveda. {\\b NO} altere ningún archivo dentro de este directorio.
|
||||
addvault.new.readme.storageLocation.3=Si se desea cifrar archivos usando Cryptomator, desbloquear la bóveda y utilizar la unidad proporcionada.
|
||||
addvault.new.readme.storageLocation.4=Si se necesita ayuda, probar %s.
|
||||
addvault.new.readme.accessLocation.fileName=BIENVENIDO A SU BÓVEDA.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUMEN CIFRADO 🔐️
|
||||
addvault.new.readme.accessLocation.2=Esta es el lugar de acceso de la bóveda. Cualquier archivo añadido a este volumen será cifrado por Cryptomator. Para acceder a este volumen en otro momento, es necesario desbloquearlo otra vez desde la aplicación de Cryptomator.
|
||||
addvault.new.readme.accessLocation.3=No dude en eliminar este archivo.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrar bóveda
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" se migró con éxito.\nYa se puede desbloquear la bóveda.
|
||||
migration.success.unlockNow=Desbloquear ahora
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Sistema de archivos no soportado
|
||||
migration.error.missingFileSystemCapabilities.description=La migración no se inició porque la bóveda se encuentra en un sistema de archivos inadecuado.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=El sistema de archivos no soporta nombres largos.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=El sistema de archivos no soporta rutas largas.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Preferencias
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtener clave de donación
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Cerrar
|
||||
main.minimizeBtn.tooltip=Minimizar
|
||||
main.preferencesBtn.tooltip=Preferencias
|
||||
main.donationKeyMissing.tooltip=Por favor, considera donar
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Se ha intentado añadir un archivo o carpeta que no p
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.changePasswordBtn=Cambiar contraseña
|
||||
vaultOptions.general.showRecoveryKeyBtn=Mostrar la clave de recuperación
|
||||
vaultOptions.general.unlockAfterStartup=Desbloquear bóveda al iniciar Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Montaje
|
||||
vaultOptions.mount.readonly=Sólo lectura
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidad asignada
|
||||
vaultOptions.mount.mountPoint.custom=Ruta personalizada
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Elegir…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Elegir un directorio vacío
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Contraseña
|
||||
vaultOptions.masterkey.changePasswordBtn=Cambiar contraseña
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Una clave de recuperación es el único medio para restaurar el acceso a una bóveda si pierde su contraseña.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Mostrar clave de recuperación
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recuperar contraseña
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Clave de recuperación
|
||||
recoveryKey.enterPassword.prompt=Ingresar la contraseña para mostrar la clave de recuperación para "%s":
|
||||
recoveryKey.display.message=La siguiente clave de recuperación puede usarse para restaurar el acceso a "%s":
|
||||
recoveryKey.display.StorageHints=Manténgala en algún lugar seguro, p.ej.:\n • Almacenarla en un administrador de contraseñas\n • Guardarla en una llave USB\n • Imprimirla en un papel
|
||||
recoveryKey.recover.prompt=Ingresar la clave de recuperación para "%s":
|
||||
recoveryKey.recover.validKey=Esta es una clave de recuperación válida
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Ingrese una contraseña nueva
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Précédent
|
||||
generic.button.cancel=Annuler
|
||||
generic.button.change=Modifier
|
||||
generic.button.copy=Copier
|
||||
generic.button.copied=Copié !
|
||||
generic.button.done=Terminé
|
||||
generic.button.next=Suivant
|
||||
generic.button.print=Imprimer
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Sans votre mot de passe, il sera d'
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Oui, mieux vaut prévenir que guérir
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Non merci, je n'oublierai pas mon mot de passe
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=QUEL EST CE RÉPERTOIRE.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FICHIERS DU COFFRE ⚠️
|
||||
addvault.new.readme.storageLocation.2=Ceci est l'emplacement de stockage de votre coffre. {\\b NE PAS} modifier les fichiers dans ce répertoire.
|
||||
addvault.new.readme.storageLocation.3=Si vous voulez chiffrer des fichiers en utilisant Cryptomator, déverrouillez le coffre et utilisez le lecteur fourni.
|
||||
addvault.new.readme.storageLocation.4=Si vous avez besoin d'aide, essayez %s.
|
||||
addvault.new.readme.accessLocation.fileName=BIENVENUE DANS VOTRE COFFRE.rft
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CHIFFRÉ 🔐️
|
||||
addvault.new.readme.accessLocation.2=Ceci est l'emplacement d'accès à votre coffre. Tous les fichiers ajoutés à ce volume seront chiffrés par Cryptomator. Pour accéder à ce volume plus tard, il suffit de le déverrouiller de nouveau à partir de l'application Cryptomator.
|
||||
addvault.new.readme.accessLocation.3=Vous pouvez supprimer ce fichier.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrer le coffre
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=“%s” migré.\nVous pouvez à présent déverrouiller ce coffre.
|
||||
migration.success.unlockNow=Déverouiller
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Système de fichiers non pris en charge
|
||||
migration.error.missingFileSystemCapabilities.description=La migration n'a pas débuté car votre coffre se trouve dans un système de fichiers inapproprié.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Ce système de fichiers ne prend pas en charge les noms de fichiers longs.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Ce système de fichiers ne prend pas en charge les chemins d'accès longs.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Préférences
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtenir une clé de don
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Fermer
|
||||
main.minimizeBtn.tooltip=Réduire
|
||||
main.preferencesBtn.tooltip=Préférences
|
||||
main.donationKeyMissing.tooltip=Merci d'envisager un don
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Vous avez essayé d’ajouter un fichier ou un dossie
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Général
|
||||
vaultOptions.general.changePasswordBtn=Modifier le mot de passe
|
||||
vaultOptions.general.showRecoveryKeyBtn=Afficher la clé de récupération
|
||||
vaultOptions.general.unlockAfterStartup=Déverrouiller le coffre au démarrage
|
||||
## Mount
|
||||
vaultOptions.mount=Montage
|
||||
vaultOptions.mount.readonly=Lecture seule
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Utiliser la lettre du lecteur assigné
|
||||
vaultOptions.mount.mountPoint.custom=Chemin personnalisé
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Choisir...
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Choisir un répertoire vide
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Mot de passe
|
||||
vaultOptions.masterkey.changePasswordBtn=Modifier le mot de passe
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Une clé de récupération est votre seul moyen de rétablir l'accès à un coffre-fort, si vous perdez votre mot de passe.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Afficher la clé de récupération
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Récupération du mot de passe
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Clé de récupération
|
||||
recoveryKey.enterPassword.prompt=Entrez votre mot de passe pour afficher la clé de récupération pour "%s":
|
||||
recoveryKey.display.message=La clé de récupération suivante peut être utilisée pour restaurer l'accès à "%s " :
|
||||
recoveryKey.display.StorageHints=Gardez-le quelque part de façon très sûr, par ex. :\n • Stockez le en utilisant un gestionnaire de mot de passe\n • Enregistrez-le sur une clé USB\n • Imprimez-le sur papier
|
||||
recoveryKey.recover.prompt=Entrez votre clé de récupération pour "%s":
|
||||
recoveryKey.recover.validKey=Cette clé de récupération est valide
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Entrer un nouveau mot de passe
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
## Start
|
||||
## Run
|
||||
## Sucess
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
## General
|
||||
@@ -50,6 +51,7 @@
|
||||
# Vault Options
|
||||
## General
|
||||
## Mount
|
||||
## Master Key
|
||||
|
||||
# Recovery Key
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Indietro
|
||||
generic.button.cancel=Annulla
|
||||
generic.button.change=Modifica
|
||||
generic.button.copy=Copia
|
||||
generic.button.copied=Copiato!
|
||||
generic.button.done=Fatto
|
||||
generic.button.next=Avanti
|
||||
generic.button.print=Stampa
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Non sarai in grado di accedere ai t
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Si, per favore, è meglio essere al sicuro
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=No grazie, non perderò la mia password
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=CHE COSA È QUESTA DIRECTORY.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FILE CASSAFORTE ⚠️
|
||||
addvault.new.readme.storageLocation.2=Questa è la posizione di archiviazione della tua cassaforte. {\\b NON} modifica qualsiasi file all'interno di questa cartella.
|
||||
addvault.new.readme.storageLocation.3=Se vuoi crittografare i file usando Cryptomator, sblocca la cassaforte e usa l'unità fornita.
|
||||
addvault.new.readme.storageLocation.4=Se hai bisogno di aiuto, prova %s.
|
||||
addvault.new.readme.accessLocation.fileName=BENVENUTO NELLA TUA CASSAFORTE.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CRIPTATO 🔐️
|
||||
addvault.new.readme.accessLocation.2=Questa è la posizione di accesso del tuo caveau. Tutti i file aggiunti a questo volume verranno crittografati da Cryptomator. Per accedere a questo volume in un secondo momento, è sufficiente sbloccarlo nuovamente dall'applicazione Cryptomator.
|
||||
addvault.new.readme.accessLocation.3=Sei libero di rimuovere questo file.
|
||||
@@ -103,6 +101,7 @@ migration.run.startMigrationBtn=Migra Cassaforte
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Migrato "%s" con successo.\nOra puoi sbloccare la tua cassaforte.
|
||||
migration.success.unlockNow=Sblocca adesso
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
preferences.title=Impostazioni
|
||||
@@ -133,6 +132,7 @@ preferences.donationKey.getDonationKey=Ottieni una chiave di donazione
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Chiudi
|
||||
main.minimizeBtn.tooltip=Minimizza
|
||||
main.preferencesBtn.tooltip=Impostazioni
|
||||
main.donationKeyMissing.tooltip=Per favore considera una donazione
|
||||
## Drag 'n' Drop
|
||||
@@ -169,8 +169,7 @@ wrongFileAlert.information=Hai provato ad aggiungere un file o una cartella che
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Generali
|
||||
vaultOptions.general.changePasswordBtn=Modifica password
|
||||
vaultOptions.general.showRecoveryKeyBtn=Visualizza la chiave di recupero
|
||||
vaultOptions.general.unlockAfterStartup=Sblocca vault all'avvio di Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Montaggio
|
||||
vaultOptions.mount.readonly=Sola lettura
|
||||
@@ -183,12 +182,18 @@ vaultOptions.mount.mountPoint.driveLetter=Usa la lettera del drive assegnata
|
||||
vaultOptions.mount.mountPoint.custom=Percorso personalizzato
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Scegli…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Scegli una directory vuota
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Password
|
||||
vaultOptions.masterkey.changePasswordBtn=Modifica password
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recupera password
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Chiave di recupero
|
||||
recoveryKey.enterPassword.prompt=Inserisci la password per visualizzare la chiave di recupero per "%s":
|
||||
recoveryKey.display.message=La seguente chiave di recupero può essere utilizzata per ripristinare l'accesso a %s":
|
||||
recoveryKey.display.StorageHints=Mantienilo da qualche parte molto sicuro, ad es.\n • Archivialo usando un gestore di password\n • Salvarlo su un'unità flash USB\n • Stamparlo su carta
|
||||
recoveryKey.recover.prompt=Inserisci la tua chiave di recupero per "%s":
|
||||
recoveryKey.recover.validKey=Questa è una chiave di recupero valida
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Inserisci una nuova password
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=戻る
|
||||
generic.button.cancel=キャンセル
|
||||
generic.button.change=変更
|
||||
generic.button.copy=コピー
|
||||
generic.button.copied=コピー完了!
|
||||
generic.button.done=完了
|
||||
generic.button.next=次へ
|
||||
generic.button.print=印刷
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=データにアクセスするに
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=はい、後悔するより手段を用意します
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=いいえ、パスワードをなくしません
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=このディレクトリについて.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ 金庫ファイル ⚠️
|
||||
addvault.new.readme.storageLocation.2=ここは金庫のストレージ先です。このディレクトリーのいずれに対しても変更を、{\\b 加えないでください} 。
|
||||
addvault.new.readme.storageLocation.3=Cryptomator を利用してファイルを暗号化するには、金庫を施錠して表示されるドライブを使用してください。
|
||||
addvault.new.readme.storageLocation.4=ヘルプが必要であれば %s をお試しください。
|
||||
addvault.new.readme.accessLocation.fileName=金庫へようこそ.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 暗号ボリューム 🔐️
|
||||
addvault.new.readme.accessLocation.2=ここは、金庫のアクセス先です。Cryptomator によりこのボリュームに追加したファイルが暗号化されます。これからは Cryptomator アプリケーションから解錠してこのボリュームにアクセスしてください。
|
||||
addvault.new.readme.accessLocation.3=このファイルはいつでも削除できます。
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=金庫を移行
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" の移行が成功しました。\n金庫を解錠できるようになりました。
|
||||
migration.success.unlockNow=今すぐ解錠
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=サポートされないファイルタイプ
|
||||
migration.error.missingFileSystemCapabilities.description=金庫が不適切なファイルシステムにあるため、移行が開始されませんでした。
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=ファイルシステムが長いファイル名をサポートしていません。
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=ファイルシステムが長いパスをサポートしていません。
|
||||
|
||||
# Preferences
|
||||
preferences.title=設定
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Donation Key を入手する
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=閉じる
|
||||
main.minimizeBtn.tooltip=最小化
|
||||
main.preferencesBtn.tooltip=設定
|
||||
main.donationKeyMissing.tooltip=寄付をお願いします
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Cryptomator の金庫ではないファイルかフ
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=基本設定
|
||||
vaultOptions.general.changePasswordBtn=パスワードの変更
|
||||
vaultOptions.general.showRecoveryKeyBtn=回復キーを表示
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomatorの起動時に金庫を解錠する
|
||||
## Mount
|
||||
vaultOptions.mount=マウント
|
||||
vaultOptions.mount.readonly=読み取り専用
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=割り当てられるドライブ文
|
||||
vaultOptions.mount.mountPoint.custom=カスタムパス
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=選択...
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=空のディレクトリを選択してください
|
||||
## Master Key
|
||||
vaultOptions.masterkey=パスワード
|
||||
vaultOptions.masterkey.changePasswordBtn=パスワードの変更
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=回復キーはパスワードを忘れてしまった場合でも、金庫へのアクセスを回復する唯一の手段です。
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示
|
||||
vaultOptions.masterkey.recoverPasswordBtn=パスワードの回復
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=回復キー
|
||||
recoveryKey.enterPassword.prompt="%s" の回復キーを表示するためのパスワードを入力してください:
|
||||
recoveryKey.display.message="%s" へのアクセス権限を復元するリカバリーキー:
|
||||
recoveryKey.display.StorageHints=とても安全な場所に保存してください、例えば:\n • パスワード管理ソフトに保存\n • USB フラッシュドライブに保存\n • 紙に印刷
|
||||
recoveryKey.recover.prompt="%s" の回復キーを入力してください:
|
||||
recoveryKey.recover.validKey=有効な回復キー
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=新しいパスワードを入力してください
|
||||
|
||||
@@ -48,12 +48,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=네. 미안한 것 보단 안전함이 더 낫습니다.
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=아니요, 나는 비밀번호를 잊지 않을겁니다.
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=이 디렉터리에 대해.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT 파일 ⚠️
|
||||
addvault.new.readme.storageLocation.2=이곳은 Vault 파일 저장경로입니다. {\\b 절대로} 이 디렉터리의 파일을 임의로 교체하거나 수정하지 마십시요.
|
||||
addvault.new.readme.storageLocation.3=Cryptomator를 사용하여 파일을 암호화하고 싶으시면, Vault를 잠금해제하고 준비된 드라이브에 사용하십시요.
|
||||
addvault.new.readme.storageLocation.4=도움이 필요하시면, %s를 시도하여 보십시요.
|
||||
addvault.new.readme.accessLocation.fileName=Vault에 오신것을 환영합니다.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 암호화된 볼륨 🔐️
|
||||
addvault.new.readme.accessLocation.2=이곳은 Vault의 접근장소입니다. 이 볼륨에 추가된 파일은 Cryptomator로 암호화 될 것입니다. 나중에 이 볼륨에 접근하기 위해서는, 단순히 Cryptomator 애플리케이션 내에서 잠금해제하여 주십시요.
|
||||
addvault.new.readme.accessLocation.3=이 파일은 지우셔도 무방합니다.
|
||||
@@ -103,6 +100,11 @@ migration.run.startMigrationBtn=Vault 마이그레이션
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" 의 마이그레이션이 성공적으로 완료되었습니다. 이제 Vault를 잠금해제할 수 있습니다.
|
||||
migration.success.unlockNow=지금 잠금해제
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=지원하지 않는 파일 시스템
|
||||
migration.error.missingFileSystemCapabilities.description=Vault가 부적절한 파일시스템에 있기 때문에 마이그레이션이 시작되지 않았습니다.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=너무 긴 파일 이름을 파일시스템에서 지원하지 않습니다.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=너무 긴 경로를 파일시스템에서 지원하지 않습니다.
|
||||
|
||||
# Preferences
|
||||
preferences.title=환경설정
|
||||
@@ -134,6 +136,7 @@ preferences.donationKey.getDonationKey=도네이션(기부) 키 얻기
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=닫기
|
||||
main.minimizeBtn.tooltip=최소화
|
||||
main.preferencesBtn.tooltip=환경설정
|
||||
main.donationKeyMissing.tooltip=기부를 부탁드립니다.
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +173,7 @@ wrongFileAlert.information=Cryptomator vault에 없는 파일이나 폴더를
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=일반
|
||||
vaultOptions.general.changePasswordBtn=비밀번호 변경
|
||||
vaultOptions.general.showRecoveryKeyBtn=복구 키 출력
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomator를 시작할 때 Vault 잠금 해제
|
||||
## Mount
|
||||
vaultOptions.mount=드라이브 구성
|
||||
vaultOptions.mount.readonly=읽기 전용
|
||||
@@ -184,12 +186,18 @@ vaultOptions.mount.mountPoint.driveLetter=드라이브 문자를 지정하여
|
||||
vaultOptions.mount.mountPoint.custom=사용자 지정 경로
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=선택
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=빈 디렉터리를 선택
|
||||
## Master Key
|
||||
vaultOptions.masterkey.changePasswordBtn=비밀번호 변경
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=복구 키는 비밀번호를 잊어버렸을 때, Vault의 접근을 복원 할 수 있는 유일한 방법입니다.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=복구 키 표시
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=복구 키
|
||||
recoveryKey.enterPassword.prompt="%s"의 복구 키를 표시하려면 비밀번호를 입력하여 주십시요.
|
||||
recoveryKey.display.message="%s" 데이터 접근을 복원하는데 사용 할 수 있는 복구 키 입니다:
|
||||
recoveryKey.display.StorageHints=매우 안전한곳에 보관하십시요. 예시:\n • 비밀번호 관리자를 사용하여 저장\n • USB 플래시 드라이브에 저장\n • 종이에 출력
|
||||
recoveryKey.recover.prompt="%s"의 복구키를 입력하십시요:
|
||||
recoveryKey.recover.validKey=올바른 복구 키 입니다.
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=새 비밀번호를 입력하십시요.
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Terug
|
||||
generic.button.cancel=Annuleren
|
||||
generic.button.change=Wijzig
|
||||
generic.button.copy=Kopieer
|
||||
generic.button.copied=Gekopieerd!
|
||||
generic.button.done=Klaar
|
||||
generic.button.next=Volgende
|
||||
generic.button.print=Afdrukken
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Zonder wachtwoord hebt u geen toega
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja graag, voorkomen is beter dan genezen
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Nee bedankt, ik verlies mijn wachtwoord niet
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=WAT IS DEZE MAP.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KLUIS BESTANDEN ⚠️
|
||||
addvault.new.readme.storageLocation.2=Dit is de opslaglocatie van uw kluis. Wijzig {\\b GEEN} bestanden binnen deze map.
|
||||
addvault.new.readme.storageLocation.3=Als u bestanden wilt versleutelen met Cryptomator, ontgrendel dan de kluis en gebruik de opgegeven schijf.
|
||||
addvault.new.readme.storageLocation.4=Als je hulp nodig hebt, probeer %s.
|
||||
addvault.new.readme.accessLocation.fileName=WELKOM IN UW KLUIS.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐 VERSLEUTELD VOLUME 🔐
|
||||
addvault.new.readme.accessLocation.2=Dit is de toegangslocatie van uw kluis. Alle bestanden die aan dit volume worden toegevoegd zullen worden versleuteld door Cryptomator. Om dit volume op een later tijdstip te kunnen gebruiken, ontgrendelt u het gewoon opnieuw vanuit de Cryptomator applicatie.
|
||||
addvault.new.readme.accessLocation.3=Voel je vrij om dit bestand te verwijderen.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Kluis migreren
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" is succesvol gemigreerd.\nU kunt nu uw kluis ontgrendelen.
|
||||
migration.success.unlockNow=Nu Ontgrendelen
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Niet ondersteund bestandssysteem
|
||||
migration.error.missingFileSystemCapabilities.description=Migratie is niet gestart, omdat uw kluis zich op een niet-adequaat bestandssysteem bevindt.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Het bestandssysteem ondersteunt geen lange bestandsnamen.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Het bestandssysteem ondersteunt geen lange paden.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Voorkeuren
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Verkrijg een donatiesleutel
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Sluiten
|
||||
main.minimizeBtn.tooltip=Minimaliseer
|
||||
main.preferencesBtn.tooltip=Voorkeuren
|
||||
main.donationKeyMissing.tooltip=Overweeg alstublieft om een donatie te doen
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=U hebt geprobeerd om een bestand of een map toe te vo
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Algemeen
|
||||
vaultOptions.general.changePasswordBtn=Wijzig wachtwoord
|
||||
vaultOptions.general.showRecoveryKeyBtn=Toon herstelsleutel
|
||||
vaultOptions.general.unlockAfterStartup=Ontgrendel kluis bij het starten van Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Aankoppelen
|
||||
vaultOptions.mount.readonly=Alleen-Lezen
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Gebruik de toegewezen schijfletter
|
||||
vaultOptions.mount.mountPoint.custom=Aangepast pad
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Kies…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Kies een lege map
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Wachtwoord
|
||||
vaultOptions.masterkey.changePasswordBtn=Wijzig wachtwoord
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Een herstelsleutel is je enige manier om de toegang tot een kluis te herstellen als je je wachtwoord kwijtraakt.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Toon herstelsleutel
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Wachtwoord herstellen
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Herstelsleutel
|
||||
recoveryKey.enterPassword.prompt=Voer uw wachtwoord in om de herstelsleutel voor "%s" te tonen:
|
||||
recoveryKey.display.message=De volgende herstelsleutel kan worden gebruikt om "%s" te herstellen:
|
||||
recoveryKey.display.StorageHints=Bewaar het op een veilige plek, bv:\n • Bewaar het in een wachtwoordmanager\n • Sla het op op een USB-stick\n • Print het op papier
|
||||
recoveryKey.recover.prompt=Voer uw herstelsleutel in voor "%s":
|
||||
recoveryKey.recover.validKey=Dit is een geldige herstelsleutel
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Voer een nieuw wachtwoord in
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
## Start
|
||||
## Run
|
||||
## Sucess
|
||||
## Missing file system capabilities
|
||||
|
||||
# Preferences
|
||||
## General
|
||||
@@ -50,6 +51,7 @@
|
||||
# Vault Options
|
||||
## General
|
||||
## Mount
|
||||
## Master Key
|
||||
|
||||
# Recovery Key
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Voltar
|
||||
generic.button.cancel=Cancelar
|
||||
generic.button.change=Alterar
|
||||
generic.button.copy=Copiar
|
||||
generic.button.copied=Copiado!
|
||||
generic.button.done=Pronto
|
||||
generic.button.next=Próximo
|
||||
generic.button.print=Imprimir
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Você não será capaz de acessar s
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Sim, por favor, melhor seguro do que arrependido
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Não, obrigado, eu não vou perder minha senha
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=O QUE É ESTE DIRETÓRIO.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARQUIVOS DO COFRE ⚠️
|
||||
addvault.new.readme.storageLocation.2=Esta é a localização do seu cofre. {\\b NÃO} altere arquivos neste diretório.
|
||||
addvault.new.readme.storageLocation.3=Se você quiser criptografar seus arquivos usando o Cryptomator, desbloqueie o cofre e use o local de armazenamento provido.
|
||||
addvault.new.readme.storageLocation.4=Se precisar de ajuda, tente %s.
|
||||
addvault.new.readme.accessLocation.fileName=BEM VINDO AO SEU COFRE.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CRIPTOGRAFADO 🔐️
|
||||
addvault.new.readme.accessLocation.2=Este é o local de acesso do seu cofre. Qualquer arquivo que você adicionar a este volume será criptografado pelo Cryptomator. Para acessar este volume posteriormente, simplesmente desbloqueie novamente o cofre na aplicação do Cryptomator.
|
||||
addvault.new.readme.accessLocation.3=Sinta-se livre para apagar este arquivo.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrar Cofre
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" migrado com sucesso.\nVocê agora pode desbloquear este cofre.
|
||||
migration.success.unlockNow=Desbloquear Agora
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Sistema de arquivos não suportado
|
||||
migration.error.missingFileSystemCapabilities.description=A migração não foi iniciada, porque o seu cofre está localizado em um sistema de arquivos inadequado.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=O sistema de arquivos não suporta arquivos com nome longo.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=O sistema de arquivos não suporta caminhos longos.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Preferências
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtenha uma chave de doação
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Fechar
|
||||
main.minimizeBtn.tooltip=Minimizar
|
||||
main.preferencesBtn.tooltip=Preferências
|
||||
main.donationKeyMissing.tooltip=Por favor, considere uma doação
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Você tentou adicionar um arquivo ou diretório que n
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Geral
|
||||
vaultOptions.general.changePasswordBtn=Alterar Senha
|
||||
vaultOptions.general.showRecoveryKeyBtn=Exibir chave de recuperação
|
||||
vaultOptions.general.unlockAfterStartup=Desbloqueie o cofre ao iniciar o Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Montagem
|
||||
vaultOptions.mount.readonly=Somente Leitura
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidade atribuída
|
||||
vaultOptions.mount.mountPoint.custom=Caminho personalizado
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Escolher…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Escolha um diretório vazio
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Senha
|
||||
vaultOptions.masterkey.changePasswordBtn=Alterar Senha
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Se você perder a sua senha, a única forma de restaurar acesso a um cofre é através de uma chave de recuperação.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Exibir chave de recuperação
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recuperar Senha
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Chave de recuperação
|
||||
recoveryKey.enterPassword.prompt=Digite sua senha para mostrar a chave de recuperação de "%s":
|
||||
recoveryKey.display.message=A seguinte chave de recuperação pode ser usada para restaurar o acesso a "%s":
|
||||
recoveryKey.display.StorageHints=Mantenha-a em um lugar bem seguro, por exemplo:\n • Guarde num gerenciador de senhas\n • Grave num flash drive USB\n • Imprima, ou escreva, num papel
|
||||
recoveryKey.recover.prompt=Digite sua chave de recuperação para "%s":
|
||||
recoveryKey.recover.validKey=Esta é uma chave de recuperação válida
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Digite a nova senha
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Назад
|
||||
generic.button.cancel=Отмена
|
||||
generic.button.change=Изменить
|
||||
generic.button.copy=Копировать
|
||||
generic.button.copied=Скопировано!
|
||||
generic.button.done=Готово
|
||||
generic.button.next=Далее
|
||||
generic.button.print=Распечатать
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получ
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, пожалуй, лучше так, чем потом сожалеть
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=ЧТО ЗА КАТАЛОГ.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ФАЙЛЫ ХРАНИЛИЩ ⚠️
|
||||
addvault.new.readme.storageLocation.2=Это место, где находится ваше хранилище. {\\b НЕ} изменяйте никакие файлы в этом каталоге.
|
||||
addvault.new.readme.storageLocation.3=Если вы хотите зашифровать файлы с помощью Cryptomator, разблокируйте хранилище и используйте предоставленный диск.
|
||||
addvault.new.readme.storageLocation.4=Нужна помощь? Попробуйте %s.
|
||||
addvault.new.readme.accessLocation.fileName=ДОБРО ПОЖАЛОВАТЬ В ХРАНИЛИЩЕ.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ЗАШИФРОВАННЫЙ ТОМ 🔐️
|
||||
addvault.new.readme.accessLocation.2=Это место доступа в хранилище. Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Чтобы получить доступ к этому тому позже, просто разблокируйте его из приложения Cryptomator.
|
||||
addvault.new.readme.accessLocation.3=Этот файл можно удалить.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Перенести хранилище
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Перенос "%s" успешно выполнен.\nТеперь можно разблокировать хранилище.
|
||||
migration.success.unlockNow=Разблокировать
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Не поддерживаемая файловая система
|
||||
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как ваше хранилище расположено в неадекватной файловой системе.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловая система не поддерживает длинные имена файлов.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловая система не поддерживает длинные пути.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Настройки
|
||||
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Получить ключ пожертв
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Закрыть
|
||||
main.minimizeBtn.tooltip=Минимизировать
|
||||
main.preferencesBtn.tooltip=Настройки
|
||||
main.donationKeyMissing.tooltip=Пожалуйста, обдумайте возможность поддержать приложение
|
||||
## Drag 'n' Drop
|
||||
@@ -170,8 +174,7 @@ wrongFileAlert.information=Вы пытались добавить файл ил
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Общие
|
||||
vaultOptions.general.changePasswordBtn=Изменить пароль
|
||||
vaultOptions.general.showRecoveryKeyBtn=Показать ключ восстановления
|
||||
vaultOptions.general.unlockAfterStartup=Разблокировать хранилище при запуске Cryptomator
|
||||
## Mount
|
||||
vaultOptions.mount=Монтирование
|
||||
vaultOptions.mount.readonly=Только чтение
|
||||
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Использовать назнач
|
||||
vaultOptions.mount.mountPoint.custom=Пользовательский путь
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Выбрать…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустой каталог
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Пароль
|
||||
vaultOptions.masterkey.changePasswordBtn=Изменить пароль
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу в случае утери пароля.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Показать ключ восстановления
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Восстановить пароль
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Ключ восстановления
|
||||
recoveryKey.enterPassword.prompt=Введите пароль, чтобы показать ключ для "%s":
|
||||
recoveryKey.display.message=Следующий ключ восстановления можно использовать для восстановления доступа к "%s":
|
||||
recoveryKey.display.StorageHints=Храните его в надежном месте, например:\n • в менеджере паролей\n • на флэш-накопителе USB\n • распечатайте на бумаге
|
||||
recoveryKey.recover.prompt=Введите ваш ключ восстановления для "%s":
|
||||
recoveryKey.recover.validKey=Это действительный ключ восстановления
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Введите новый пароль
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Bakåt
|
||||
generic.button.cancel=Avbryt
|
||||
generic.button.change=Ändra
|
||||
generic.button.copy=Kopiera
|
||||
generic.button.copied=Kopierad!
|
||||
generic.button.done=Klar
|
||||
generic.button.next=Nästa
|
||||
generic.button.print=Skriv ut
|
||||
@@ -30,7 +31,7 @@ addvaultwizard.welcome.newButton=Skapa nytt valv
|
||||
addvaultwizard.welcome.existingButton=Öppna befintligt valv
|
||||
## New
|
||||
### Name
|
||||
addvaultwizard.new.nameInstruction=Välj namn för valvet
|
||||
addvaultwizard.new.nameInstruction=Ange namn för valvet
|
||||
addvaultwizard.new.namePrompt=Valvets namn
|
||||
### Location
|
||||
addvaultwizard.new.locationInstruction=Var ska Cryptomator lagra de krypterade filerna för ditt valv?
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Du kommer inte ha tillgång till di
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja tack. För säkerhets skull
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Nej tack. Här slarvas inga lösenord bort
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=VAD ÄR DETTA FÖR KATALOG.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VALV-FILER ⚠️
|
||||
addvault.new.readme.storageLocation.2=Detta är ditt valvs lagringsplats. {\\b ÄNDRA INTE} någon fil i denna katalog.
|
||||
addvault.new.readme.storageLocation.3=Om du vill kryptera filer med Cryptomator, lås upp valvet och använd angiven enhet.
|
||||
addvault.new.readme.storageLocation.4=Om du behöver hjälp, prova %s.
|
||||
addvault.new.readme.accessLocation.fileName=VÄLKOMMEN TILL DITT VALV.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ KRYPTERAD VOLYM 🔐️
|
||||
addvault.new.readme.accessLocation.2=Detta är ditt valvs åtkomstplats. Filer som läggs till i denna volym kommer att krypteras av Cryptomator. För att komma åt denna volym senare, lås bara upp den igen från Cryptomator-programmet.
|
||||
addvault.new.readme.accessLocation.3=Du kan ta bort denna fil.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrera valv
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" migrerades fullständigt.\nDu kan nu låsa upp ditt valv.
|
||||
migration.success.unlockNow=Lås upp nu
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Filsystemet stöds ej
|
||||
migration.error.missingFileSystemCapabilities.description=Migreringen startades inte, eftersom ditt valv ligger på ett ogiltigt filsystem.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Filsystemet har inte stöd för långa filnamn.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Filsystemet har inte stöd för långa filnamn.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Inställningar
|
||||
@@ -112,6 +115,9 @@ preferences.general.theme=Utseende
|
||||
preferences.general.startHidden=Dölj fönster när Cryptomator startar
|
||||
preferences.general.debugLogging=Aktivera debug loggning
|
||||
preferences.general.autoStart=Starta Cryptomator vid systemstart
|
||||
preferences.general.interfaceOrientation=Gränssnittsjustering
|
||||
preferences.general.interfaceOrientation.ltr=Vänster till höger
|
||||
preferences.general.interfaceOrientation.rtl=Höger till vänster
|
||||
## Volume
|
||||
preferences.volume=Virtuell enhet
|
||||
preferences.volume.type=Volym-typ
|
||||
@@ -131,6 +137,7 @@ preferences.donationKey.getDonationKey=Skaffa en donationsnyckel
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Stäng
|
||||
main.minimizeBtn.tooltip=Minimera
|
||||
main.preferencesBtn.tooltip=Inställningar
|
||||
main.donationKeyMissing.tooltip=Överväg gärna en donation
|
||||
## Drag 'n' Drop
|
||||
@@ -167,8 +174,7 @@ wrongFileAlert.information=Du försöker lägga till en fil eller katalog som in
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Allmänt
|
||||
vaultOptions.general.changePasswordBtn=Ändra lösenord
|
||||
vaultOptions.general.showRecoveryKeyBtn=Visa Återsällningsnyckel
|
||||
vaultOptions.general.unlockAfterStartup=Lås upp valvet när Cryptomator startas
|
||||
## Mount
|
||||
vaultOptions.mount=Montering
|
||||
vaultOptions.mount.readonly=Skrivskyddad
|
||||
@@ -181,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Använd tilldelad enhetsbokstav
|
||||
vaultOptions.mount.mountPoint.custom=Anpassad sökväg
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Välj…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Välj en tom katalog
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Lösenord
|
||||
vaultOptions.masterkey.changePasswordBtn=Ändra lösenord
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=En återställningsnyckel är ditt enda sätt att återställa åtkomst till ett valv, om du förlorar ditt lösenord.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Visa återsällningsnyckel
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Återställ lösenord
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Återställningsnyckel
|
||||
recoveryKey.enterPassword.prompt=Ange ditt lösenord för att visa återställningsnyckeln för "%s":
|
||||
recoveryKey.display.message=Använd denna återställningsnyckel för att återställa åtkomst till "%s":
|
||||
recoveryKey.display.StorageHints=Spara den på en säker plats, t.ex:\n • I en lösenordshanterare\n • På ett USB-minne (förvara säkert) \n • Skriv ut på ett papper (förvara säkert)
|
||||
recoveryKey.recover.prompt=Ange din återställningsnyckel för "%s":
|
||||
recoveryKey.recover.validKey=Detta är en giltig återställningsnyckel
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Ange ett nytt lösenord
|
||||
|
||||
@@ -7,6 +7,7 @@ generic.button.back=Geri
|
||||
generic.button.cancel=İptal
|
||||
generic.button.change=Değiştir
|
||||
generic.button.copy=Kopyala
|
||||
generic.button.copied=Kopyalandı!
|
||||
generic.button.done=Bitti
|
||||
generic.button.next=İleri
|
||||
generic.button.print=Yazdır
|
||||
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Şifreniz olmadan verilerinize eri
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Evet lütfen, kafamı taşlara vurmaktan iyidir
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Hayır teşekkürler, şifremi kaybetmeyeceğim
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=BU DİZİN NEDİR.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KASA DOSYALARI ⚠️
|
||||
addvault.new.readme.storageLocation.2=Bu kasanızın depolama yeri. Bu dizindeki herhangi bir dosyayı asla {\\b DEĞİŞTİRMEYİN}.
|
||||
addvault.new.readme.storageLocation.3=Dosyaları Cryptomator kullanarak şifrelemek istiyorsanız kasanın kilidini açın ve verilen sürücüyü kullanın.
|
||||
addvault.new.readme.storageLocation.4=Eğer yardıma ihtiyacınız varsa, %s 'ı deneyin.
|
||||
addvault.new.readme.accessLocation.fileName=KASANIZA HOŞGELDİNİZ.rtf
|
||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ŞİFRELENMİŞ BİRİM 🔐️
|
||||
addvault.new.readme.accessLocation.2=Bu kasanızın erişim yeri. Bu birime eklenen dosyalar Cryptomator tarafından şifrelenir. Bu birime daha sonra bir daha erişmek için Cryptomator uygulamasından tekrar kilidini kaldırmanız yeterlidir.
|
||||
addvault.new.readme.accessLocation.3=Bu dosyayı silmeye çekinmeyin.
|
||||
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Kasayı Taşı
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" başarıyla taşındı.\nKasanızın kilidini şimdi kaldırabilirsiniz.
|
||||
migration.success.unlockNow=Kilidi Şimdi Aç
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Desteklenmeyen Dosya Sistemi
|
||||
migration.error.missingFileSystemCapabilities.description=Kasanız desteklenmeyen bir dosya sisteminde bulunduğundan taşıma başlatılmadı.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Dosya sistemi, uzun belge adlarını desteklemiyor.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Dosya sistemi, uzun yolları desteklemiyor.
|
||||
|
||||
# Preferences
|
||||
preferences.title=Seçenekler
|
||||
@@ -111,7 +114,7 @@ preferences.general=Genel
|
||||
preferences.general.theme=Görünüş ve Davranış
|
||||
preferences.general.startHidden=Cryptomator'ı başlatırken pencereyi gizle
|
||||
preferences.general.debugLogging=Hata ayıklama günlüğünü etkinleştir
|
||||
preferences.general.autoStart=Cryptomator'u sistrm başlangıcında çalıştır
|
||||
preferences.general.autoStart=Cryptomator'u sistem başlangıcında çalıştır
|
||||
preferences.general.interfaceOrientation=Arayüz Yönü
|
||||
preferences.general.interfaceOrientation.ltr=Sola Yaslı
|
||||
preferences.general.interfaceOrientation.rtl=Sağa Yaslı
|
||||
@@ -129,11 +132,12 @@ preferences.updates.updateAvailable=%s sürümüne güncelleme mevcut.
|
||||
## Donation Key
|
||||
preferences.donationKey=Bağış
|
||||
preferences.donationKey.registeredFor=%s için kaydedildi
|
||||
preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bağış anahtarı dediğimiz şey, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
|
||||
preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bu anahtar, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
|
||||
preferences.donationKey.getDonationKey=Bir bağış anahtarı al
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Kapat
|
||||
main.minimizeBtn.tooltip=Simge Durumuna Küçült
|
||||
main.preferencesBtn.tooltip=Seçenekler
|
||||
main.donationKeyMissing.tooltip=Bağış yapmayı düşünmez miydiniz?
|
||||
## Drag 'n' Drop
|
||||
@@ -170,10 +174,9 @@ wrongFileAlert.information=Cryptomator kasası gibi görünmeyen bir dosya veya
|
||||
# Vault Options
|
||||
## General
|
||||
vaultOptions.general=Genel
|
||||
vaultOptions.general.changePasswordBtn=Şifreyi Değiştir
|
||||
vaultOptions.general.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomator başlatıldığında kasayı aç
|
||||
## Mount
|
||||
vaultOptions.mount=Bağlanılıyor
|
||||
vaultOptions.mount=Bağlantı
|
||||
vaultOptions.mount.readonly=Salt-Okunur
|
||||
vaultOptions.mount.driveName=Sürücü Adı
|
||||
vaultOptions.mount.customMountFlags=Özel Bağlantı Parametreleri
|
||||
@@ -184,12 +187,19 @@ vaultOptions.mount.mountPoint.driveLetter=Bir sürücü harfi kullan
|
||||
vaultOptions.mount.mountPoint.custom=Özel Dosya Yolu
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Seç…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Boş bir dizin seçin
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Şifre
|
||||
vaultOptions.masterkey.changePasswordBtn=Şifreyi Değiştir
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Parolanızı Kurtarın
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Kurtarma Anahtarı
|
||||
recoveryKey.enterPassword.prompt="%s" için kurtarma anahtarını göstermek üzere şifrenizi girin:
|
||||
recoveryKey.display.message=Aşağıdaki kurtarma anahtarı "%s" kasasına erişimi kazanmak için kullanılabilir:
|
||||
recoveryKey.display.StorageHints=Bunu çok güvenli bir yerde saklayın, örneğin:\n • Şifre yöneticisi kullanarak depolayın\n • USB flash belleğe kaydedin\n • Bir sayfaya yazdırın
|
||||
recoveryKey.recover.prompt="%s" için kurtarma anahtarınızı girin:
|
||||
recoveryKey.recover.validKey=Bu geçerli bir kurtarma anahtarı
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Yeni bir şifre girin
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user