mirror of
https://github.com/cryptomator/cryptomator.git
synced 2024-11-27 05:50:26 +00:00
Merge branch 'develop' into feature/mount-provider
# Conflicts: # pom.xml # src/main/java/org/cryptomator/common/vaults/Vault.java # src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java
This commit is contained in:
commit
90408504e2
7
.github/workflows/win-exe.yml
vendored
7
.github/workflows/win-exe.yml
vendored
@ -118,6 +118,9 @@ jobs:
|
||||
- name: Fix permissions
|
||||
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
||||
shell: pwsh
|
||||
- name: Extract integrations DLL for code signing
|
||||
shell: pwsh
|
||||
run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll }
|
||||
- name: Codesign
|
||||
uses: skymatic/code-sign-action@v2
|
||||
with:
|
||||
@ -128,6 +131,10 @@ jobs:
|
||||
timestampUrl: 'http://timestamp.digicert.com'
|
||||
folder: appdir/Cryptomator
|
||||
recursive: true
|
||||
- name: Repack signed DLL into jar
|
||||
shell: pwsh
|
||||
run: |
|
||||
gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll}
|
||||
- name: Generate license for MSI
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
|
25
dist/linux/appimage/build.sh
vendored
25
dist/linux/appimage/build.sh
vendored
@ -11,15 +11,20 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
|
||||
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
SEMVER_STR=${VERSION}
|
||||
|
||||
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||
|
||||
# compile
|
||||
mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux
|
||||
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
|
||||
cp ../../../LICENSE.txt ../../../target
|
||||
cp ../launcher.sh ../../../target
|
||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||
|
||||
# add runtime
|
||||
${JAVA_HOME}/bin/jlink \
|
||||
--verbose \
|
||||
--output runtime \
|
||||
--module-path "${JAVA_HOME}/jmods" \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||
--strip-native-commands \
|
||||
--no-header-files \
|
||||
--no-man-pages \
|
||||
@ -27,7 +32,7 @@ ${JAVA_HOME}/bin/jlink \
|
||||
--compress=1
|
||||
|
||||
# create app dir
|
||||
envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
||||
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
|
||||
${JAVA_HOME}/bin/jpackage \
|
||||
--verbose \
|
||||
--type app-image \
|
||||
@ -35,7 +40,7 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
--input ../../../target/libs \
|
||||
--module-path ../../../target/mods \
|
||||
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \
|
||||
--dest . \
|
||||
--dest appdir \
|
||||
--name Cryptomator \
|
||||
--vendor "Skymatic GmbH" \
|
||||
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
|
||||
@ -46,6 +51,7 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
|
||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
|
||||
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
|
||||
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
|
||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
|
||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
|
||||
--java-options "-Dcryptomator.showTrayIcon=false" \
|
||||
@ -54,9 +60,8 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
--resource-dir ../resources
|
||||
|
||||
# transform AppDir
|
||||
mv Cryptomator Cryptomator.AppDir
|
||||
mv appdir/Cryptomator Cryptomator.AppDir
|
||||
cp -r resources/AppDir/* Cryptomator.AppDir/
|
||||
chmod +x Cryptomator.AppDir/lib/runtime/bin/java
|
||||
envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh
|
||||
cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png
|
||||
cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png
|
||||
@ -83,5 +88,11 @@ chmod +x /tmp/appimagetool.AppImage
|
||||
# create AppImage
|
||||
/tmp/appimagetool.AppImage \
|
||||
Cryptomator.AppDir \
|
||||
cryptomator-SNAPSHOT-x86_64.AppImage \
|
||||
cryptomator-${SEMVER_STR}-x86_64.AppImage \
|
||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
||||
|
||||
echo ""
|
||||
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage"
|
||||
echo ""
|
||||
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
|
||||
echo ""
|
10
pom.xml
10
pom.xml
@ -29,11 +29,11 @@
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptolib.version>2.1.1</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>2.5.3</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.2.0-beta3</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
||||
<cryptomator.cryptofs.version>2.6.1</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.2.0-beta4</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.2.0-beta1</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.2.0-beta1</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.2.0-beta1</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>2.0.0-beta3</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>2.0.0-beta2</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>2.0.0-beta2</cryptomator.webdav.version>
|
||||
|
@ -67,7 +67,6 @@ public class Vault {
|
||||
private final BooleanBinding needsMigration;
|
||||
private final BooleanBinding unknownError;
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final WindowsDriveLetters windowsDriveLetters;
|
||||
private final Mounter mounter;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
@ -89,7 +88,6 @@ public class Vault {
|
||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.windowsDriveLetters = windowsDriveLetters;
|
||||
this.mounter = mounter;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
}
|
||||
@ -316,6 +314,22 @@ public class Vault {
|
||||
return vaultSettings.path().getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets from the cleartext path its ciphertext counterpart.
|
||||
* The cleartext path has to start from the vault root (by starting with "/").
|
||||
*
|
||||
* @return Local os path to the ciphertext resource
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public Path getCiphertextPath(String cleartextPath) throws IOException {
|
||||
if (!cleartextPath.startsWith("/")) {
|
||||
throw new IllegalArgumentException("Input path must be absolute from vault root by starting with \"/\".");
|
||||
}
|
||||
var fs = cryptoFileSystem.get();
|
||||
var cryptoPath = fs.getPath(cleartextPath);
|
||||
return fs.getCiphertextPath(cryptoPath);
|
||||
}
|
||||
|
||||
public VaultConfigCache getVaultConfigCache() {
|
||||
return configCache;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ public enum FontAwesome5Icon {
|
||||
EYE_SLASH("\uF070"), //
|
||||
FAST_FORWARD("\uF050"), //
|
||||
FILE("\uF15B"), //
|
||||
FILE_DOWNLOAD("\uF56D"), //
|
||||
FILE_IMPORT("\uF56F"), //
|
||||
FOLDER_OPEN("\uF07C"), //
|
||||
FUNNEL("\uF0B0"), //
|
||||
|
@ -3,33 +3,17 @@ package org.cryptomator.ui.mainwindow;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.DirStructure;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@MainWindowScoped
|
||||
public class MainWindowController implements FxController {
|
||||
@ -37,28 +21,19 @@ public class MainWindowController implements FxController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
|
||||
public StackPane root;
|
||||
|
||||
@Inject
|
||||
public MainWindowController(@MainWindow Stage window, VaultListManager vaultListManager, ObjectProperty<Vault> selectedVault, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
public MainWindowController(@MainWindow Stage window, ObjectProperty<Vault> selectedVault) {
|
||||
this.window = window;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.selectedVault = selectedVault;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOG.trace("init MainWindowController");
|
||||
root.setOnDragEntered(this::handleDragEvent);
|
||||
root.setOnDragOver(this::handleDragEvent);
|
||||
root.setOnDragDropped(this::handleDragEvent);
|
||||
root.setOnDragExited(this::handleDragEvent);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
root.getStyleClass().add("os-windows");
|
||||
}
|
||||
@ -72,65 +47,4 @@ public class MainWindowController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDragEvent(DragEvent event) {
|
||||
if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
|
||||
draggingOver.set(true);
|
||||
} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
|
||||
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
|
||||
if (vaultPaths.isEmpty()) {
|
||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||
} else {
|
||||
vaultPaths.forEach(this::addVault);
|
||||
}
|
||||
event.setDropCompleted(!vaultPaths.isEmpty());
|
||||
event.consume();
|
||||
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||
draggingOver.set(false);
|
||||
draggingVaultOver.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsVault(Path path) {
|
||||
try {
|
||||
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
path = path.getParent();
|
||||
}
|
||||
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addVault(Path pathToVault) {
|
||||
try {
|
||||
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
vaultListManager.add(pathToVault.getParent());
|
||||
} else {
|
||||
vaultListManager.add(pathToVault);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Not a vault: {}", pathToVault);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public BooleanProperty draggingOverProperty() {
|
||||
return draggingOver;
|
||||
}
|
||||
|
||||
public boolean isDraggingOver() {
|
||||
return draggingOver.get();
|
||||
}
|
||||
|
||||
public BooleanProperty draggingVaultOverProperty() {
|
||||
return draggingVaultOver;
|
||||
}
|
||||
|
||||
public boolean isDraggingVaultOver() {
|
||||
return draggingVaultOver.get();
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,86 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.net.URI;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnlockedController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
|
||||
private static final String ACTIVE_CLASS = "active";
|
||||
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final VaultService vaultService;
|
||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
private final Stage mainWindow;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
||||
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
||||
private final ObservableValue<Boolean> accessibleViaPath;
|
||||
private final ObservableValue<Boolean> accessibleViaUri;
|
||||
private final ObservableValue<String> mountPoint;
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
|
||||
|
||||
//FXML
|
||||
public Button dropZone;
|
||||
|
||||
@Inject
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, ResourceBundle resourceBundle) {
|
||||
this.vault = vault;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultService = vaultService;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
this.mainWindow = mainWindow;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
||||
this.vaultStatsBuilder = vaultStatsBuilder;
|
||||
var mp = vault.flatMap(Vault::mountPointProperty);
|
||||
this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
|
||||
this.accessibleViaUri = mp.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
|
||||
this.mountPoint = mp.map(m -> {
|
||||
if(m instanceof Mountpoint.WithPath mwp) {
|
||||
if (m instanceof Mountpoint.WithPath mwp) {
|
||||
return mwp.path().toString();
|
||||
} else {
|
||||
return m.uri().toASCIIString();
|
||||
@ -58,6 +88,33 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
});
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
dropZone.setOnDragEntered(this::handleDragEvent);
|
||||
dropZone.setOnDragOver(this::handleDragEvent);
|
||||
dropZone.setOnDragDropped(this::handleDragEvent);
|
||||
dropZone.setOnDragExited(this::handleDragEvent);
|
||||
|
||||
EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
|
||||
}
|
||||
|
||||
private void handleDragEvent(DragEvent event) {
|
||||
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
event.acceptTransferModes(TransferMode.LINK);
|
||||
draggingOver.set(true);
|
||||
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
|
||||
if (ciphertextPaths.isEmpty()) {
|
||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||
} else {
|
||||
revealOrCopyPaths(ciphertextPaths);
|
||||
}
|
||||
event.setDropCompleted(!ciphertextPaths.isEmpty());
|
||||
event.consume();
|
||||
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||
draggingOver.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
||||
return vaultStatsBuilder.vault(vault).build();
|
||||
}
|
||||
@ -69,7 +126,6 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void copyMountUri() {
|
||||
//TODO: add popup that conent is copied
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(mountPoint.getValue());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
@ -85,6 +141,77 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseFileAndReveal() {
|
||||
Preconditions.checkState(accessibleViaPath.getValue());
|
||||
var fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
|
||||
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
|
||||
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
|
||||
if (cleartextFile != null) {
|
||||
var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
|
||||
revealOrCopyPaths(ciphertextPaths);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startsWithVaultAccessPoint(Path path) {
|
||||
return path.startsWith(Path.of(mountPoint.getValue()));
|
||||
}
|
||||
|
||||
private Optional<Path> getCiphertextPath(Path path) {
|
||||
if (!startsWithVaultAccessPoint(path)) {
|
||||
LOG.debug("Path does not start with access point of selected vault: {}", path);
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
var accessPoint = mountPoint.getValue();
|
||||
var cleartextPath = path.toString().substring(accessPoint.length());
|
||||
if (!cleartextPath.startsWith("/")) {
|
||||
cleartextPath = "/" + cleartextPath;
|
||||
}
|
||||
return Optional.of(vault.get().getCiphertextPath(cleartextPath));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to get ciphertext path from path: {}", path);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void revealOrCopyPaths(List<Path> paths) {
|
||||
if (!revealPaths(paths)) {
|
||||
LOG.warn("No service provider to reveal files found.");
|
||||
copyPathsToClipboard(paths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveals the paths over the {@link RevealPathService} in the file system
|
||||
*
|
||||
* @param paths List of Paths to reveal
|
||||
* @return true, if at least one service provider was present, false otherwise
|
||||
*/
|
||||
private boolean revealPaths(List<Path> paths) {
|
||||
return RevealPathService.get().findAny().map(s -> {
|
||||
paths.forEach(path -> {
|
||||
try {
|
||||
s.reveal(path);
|
||||
} catch (RevealFailedException e) {
|
||||
LOG.error("Revealing ciphertext file failed.", e);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}).orElse(false);
|
||||
}
|
||||
|
||||
private void copyPathsToClipboard(List<Path> paths) {
|
||||
StringBuilder clipboardString = new StringBuilder();
|
||||
paths.forEach(p -> clipboardString.append(p.toString()).append("\n"));
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, clipboardString.toString()));
|
||||
ciphertextPathsCopied.setValue(true);
|
||||
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
ciphertextPathsCopied.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
@ -119,5 +246,11 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
return mountPoint.getValue();
|
||||
}
|
||||
|
||||
public BooleanProperty ciphertextPathsCopiedProperty() {
|
||||
return ciphertextPathsCopied;
|
||||
}
|
||||
|
||||
public boolean isCiphertextPathsCopied() {
|
||||
return ciphertextPathsCopied.get();
|
||||
}
|
||||
}
|
||||
|
@ -3,26 +3,43 @@ package org.cryptomator.ui.mainwindow;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.DirStructure;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||
@ -31,6 +48,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||
@MainWindowScoped
|
||||
public class VaultListController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
||||
|
||||
private final Stage mainWindow;
|
||||
private final ObservableList<Vault> vaults;
|
||||
@ -39,17 +57,21 @@ public class VaultListController implements FxController {
|
||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||
private final BooleanBinding emptyVaultList;
|
||||
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
|
||||
public ListView<Vault> vaultList;
|
||||
public StackPane root;
|
||||
|
||||
@Inject
|
||||
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue) {
|
||||
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue, VaultListManager vaultListManager) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
this.cellFactory = cellFactory;
|
||||
this.addVaultWizard = addVaultWizard;
|
||||
this.removeVaultDialogue = removeVaultDialogue;
|
||||
this.vaultListManager = vaultListManager;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
|
||||
@ -100,6 +122,11 @@ public class VaultListController implements FxController {
|
||||
keyEvent.consume();
|
||||
}
|
||||
});
|
||||
|
||||
root.setOnDragEntered(this::handleDragEvent);
|
||||
root.setOnDragOver(this::handleDragEvent);
|
||||
root.setOnDragDropped(this::handleDragEvent);
|
||||
root.setOnDragExited(this::handleDragEvent);
|
||||
}
|
||||
|
||||
private void deselect(MouseEvent released) {
|
||||
@ -128,6 +155,47 @@ public class VaultListController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDragEvent(DragEvent event) {
|
||||
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
|
||||
if (draggingVaultOver.get()) {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
}
|
||||
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
|
||||
if (!vaultPaths.isEmpty()) {
|
||||
vaultPaths.forEach(this::addVault);
|
||||
}
|
||||
event.setDropCompleted(!vaultPaths.isEmpty());
|
||||
event.consume();
|
||||
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||
draggingVaultOver.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsVault(Path path) {
|
||||
try {
|
||||
if (path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
path = path.getParent();
|
||||
}
|
||||
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addVault(Path pathToVault) {
|
||||
try {
|
||||
if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) {
|
||||
vaultListManager.add(pathToVault.getParent());
|
||||
} else {
|
||||
vaultListManager.add(pathToVault);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Not a vault: {}", pathToVault);
|
||||
}
|
||||
}
|
||||
|
||||
// Getter and Setter
|
||||
|
||||
public BooleanBinding emptyVaultListProperty() {
|
||||
@ -138,4 +206,13 @@ public class VaultListController implements FxController {
|
||||
return emptyVaultList.get();
|
||||
}
|
||||
|
||||
public BooleanProperty draggingVaultOverProperty() {
|
||||
return draggingVaultOver;
|
||||
}
|
||||
|
||||
public boolean isDraggingVaultOver() {
|
||||
return draggingVaultOver.get();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -204,16 +204,6 @@
|
||||
-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: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||
-fx-background-color: SECONDARY;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* TabPane *
|
||||
@ -884,3 +874,40 @@
|
||||
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Drag and Drop *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.drag-n-drop-border {
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.button.drag-n-drop {
|
||||
-fx-background-color: CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 1.4em 1em 1.4em 1em;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-border-style: dashed inside;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
||||
.button.drag-n-drop:focused {
|
||||
-fx-border-color: CONTROL_BORDER_FOCUSED;
|
||||
}
|
||||
|
||||
.button.drag-n-drop:armed {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.button.drag-n-drop.active {
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-style: solid inside;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
@ -203,16 +203,6 @@
|
||||
-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: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||
-fx-background-color: SECONDARY;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* TabPane *
|
||||
@ -883,3 +873,40 @@
|
||||
-fx-fill: linear-gradient(to bottom, PRIMARY, transparent);
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Drag and Drop *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.drag-n-drop-border {
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-width: 3px;
|
||||
}
|
||||
|
||||
.button.drag-n-drop {
|
||||
-fx-background-color: CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 1.4em 1em 1.4em 1em;
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-border-style: dashed inside;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
||||
.button.drag-n-drop:focused {
|
||||
-fx-border-color: CONTROL_BORDER_FOCUSED;
|
||||
}
|
||||
|
||||
.button.drag-n-drop:armed {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.button.drag-n-drop.active {
|
||||
-fx-border-color: SECONDARY;
|
||||
-fx-border-style: solid inside;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.SplitPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
@ -14,24 +10,10 @@
|
||||
styleClass="main-window">
|
||||
<VBox minWidth="650">
|
||||
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
|
||||
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
||||
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
||||
</SplitPane>
|
||||
|
||||
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
|
||||
<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
|
||||
<Label text="%main.dropZone.unknownDragboardContent"/>
|
||||
</HBox>
|
||||
<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
<Label text="%main.dropZone.dropVault"/>
|
||||
</HBox>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS">
|
||||
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
||||
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
||||
</SplitPane>
|
||||
</VBox>
|
||||
<fx:include source="main_window_resize.fxml"/>
|
||||
</StackPane>
|
||||
|
@ -5,6 +5,8 @@
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
|
||||
@ -41,7 +43,28 @@
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<HBox alignment="BOTTOM_RIGHT">
|
||||
<HBox alignment="BOTTOM_CENTER">
|
||||
<HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="0"/>
|
||||
</padding>
|
||||
<Button fx:id="dropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
|
||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
|
||||
<graphic>
|
||||
<VBox spacing="6">
|
||||
|
@ -8,25 +8,29 @@
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
|
||||
minWidth="206">
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
|
||||
<contextMenu>
|
||||
<fx:include source="vault_list_contextmenu.fxml"/>
|
||||
</contextMenu>
|
||||
</ListView>
|
||||
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" 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>
|
||||
<FontAwesome5IconView glyph="PLUS"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</VBox>
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:id="root"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultListController"
|
||||
minWidth="206">
|
||||
<VBox>
|
||||
<StackPane VBox.vgrow="ALWAYS">
|
||||
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
|
||||
<contextMenu>
|
||||
<fx:include source="vault_list_contextmenu.fxml"/>
|
||||
</contextMenu>
|
||||
</ListView>
|
||||
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" 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>
|
||||
<FontAwesome5IconView glyph="PLUS"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</VBox>
|
||||
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
||||
</StackPane>
|
||||
|
@ -343,9 +343,6 @@ main.minimizeBtn.tooltip=Minimize
|
||||
main.preferencesBtn.tooltip=Preferences
|
||||
main.debugModeEnabled.tooltip=Debug mode is enabled
|
||||
main.supporterCertificateMissing.tooltip=Please consider donating
|
||||
## Drag 'n' Drop
|
||||
main.dropZone.dropVault=Add this vault
|
||||
main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
|
||||
## Vault List
|
||||
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
||||
main.vaultlist.contextMenu.remove=Remove…
|
||||
@ -376,6 +373,10 @@ main.vaultDetail.throughput.idle=idle
|
||||
main.vaultDetail.throughput.kbps=%.1f kiB/s
|
||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||
main.vaultDetail.stats=Vault Statistics
|
||||
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
|
||||
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
|
||||
main.vaultDetail.filePickerTitle=Select File Inside Vault
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
||||
main.vaultDetail.missing.recheck=Recheck
|
||||
|
Loading…
Reference in New Issue
Block a user