Implement file browser download window

This commit is contained in:
crschnick 2023-03-30 18:56:18 +00:00
parent 7fd2e89c77
commit 2763ca40c8
11 changed files with 246 additions and 11 deletions

View file

@ -38,7 +38,11 @@ public class FileBrowserComp extends SimpleComp {
@Override
protected Region createSimple() {
var bookmarksList = new BookmarkList(model).createRegion();
var splitPane = new SplitPane(bookmarksList, createTabs());
var localDownloadStage = new LocalFileTransferComp(model.getLocalTransfersStage()).createRegion();
var vertical = new VBox(bookmarksList, localDownloadStage);
vertical.setFillWidth(true);
var splitPane = new SplitPane(vertical, createTabs());
splitPane
.widthProperty()
.addListener(

View file

@ -40,6 +40,7 @@ public class FileBrowserModel {
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
private final LocalFileTransferStage localTransfersStage = new LocalFileTransferStage();
public void finishChooser() {
if (getMode().equals(Mode.BROWSER)) {

View file

@ -0,0 +1,20 @@
package io.xpipe.app.browser;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.SimpleStringProperty;
public class FileIcons {
public static PrettyImageComp createIcon(FileSystem.FileEntry entry) {
return new PrettyImageComp(new SimpleStringProperty(getIcon(entry)), 22, 22);
}
public static String getIcon(FileSystem.FileEntry entry) {
if (!entry.isDirectory()) {
return "app:file_drag_icon.png";
} else {
return "app:folder_closed.svg";
}
}
}

View file

@ -1,10 +1,11 @@
package io.xpipe.app.browser;
import io.xpipe.app.core.AppResources;
import io.xpipe.core.store.FileSystem;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.*;
import lombok.Getter;
@ -124,13 +125,15 @@ public class FileListCompEntry {
return;
}
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "img/file_drag_icon.png")
.orElseThrow();
var image = new Image(url.toString(), 80, 80, true, false);
var selected = model.getSelected();
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
db.setContent(FileBrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected));
db.setDragView(image, 30, 60);
var r = new SelectedFileListComp(selected).createRegion();
new Scene(r);
WritableImage image = r.snapshot(new SnapshotParameters(), null);
db.setDragView(image, -20, 15);
event.setDragDetect(true);
event.consume();
}

View file

@ -72,9 +72,15 @@ public class FileSystemHelper {
return FileNames.toDirectory(normalized);
}
private static FileSystem localFileSystem;
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
if (localFileSystem == null) {
localFileSystem = new LocalStore().createFileSystem();
}
return new FileSystem.FileEntry(
LocalStore.getFileSystem(),
localFileSystem,
file.toString(),
Files.getLastModifiedTime(file).toInstant(),
Files.isDirectory(file),

View file

@ -0,0 +1,104 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.IOException;
import java.util.List;
public class LocalFileTransferComp extends SimpleComp {
private final LocalFileTransferStage stage;
public LocalFileTransferComp(LocalFileTransferStage stage) {
this.stage = stage;
}
@Override
protected Region createSimple() {
var background = new LabelComp(AppI18n.observable("download"))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
.visible(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())));
var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = BindingsHelper.mappedContentBinding(stage.getItems(), item -> item.getFileEntry());
var list = new SelectedFileListComp(binding).apply(struc -> struc.get().setMinHeight(200));
var dragNotice = new LabelComp(AppI18n.observable("dragFiles"))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export")))
.hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())))
.grow(true, false)
.apply(struc -> struc.get().setPadding(new Insets(8)));
var loading = new LoadingOverlayComp(
new VerticalComp(List.of(list, dragNotice)), PlatformThread.sync(stage.getDownloading()));
var stack = new StackComp(List.of(backgroundStack, loading)).apply(struc -> {
struc.get().setOnDragOver(event -> {
// Accept drops from inside the app window
if (event.getGestureSource() != null) {
event.acceptTransferModes(TransferMode.ANY);
event.consume();
}
});
struc.get().setOnDragDropped(event -> {
if (event.getGestureSource() != null) {
var files = FileBrowserClipboard.retrieveDrag(event.getDragboard())
.getEntries();
stage.drop(files);
event.setDropCompleted(true);
event.consume();
}
});
struc.get().setOnDragDetected(event -> {
if (stage.getDownloading().get()) {
return;
}
var files = stage.getItems().stream()
.map(item -> {
try {
return item.getLocalFile().toRealPath().toFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.toList();
Dragboard db = struc.get().startDragAndDrop(TransferMode.MOVE);
var cc = new ClipboardContent();
cc.putFiles(files);
db.setContent(cc);
var r = new SelectedFileListComp(FXCollections.observableList(stage.getItems().stream()
.map(item -> item.getFileEntry())
.toList()))
.createRegion();
new Scene(r);
WritableImage image = r.snapshot(new SnapshotParameters(), null);
db.setDragView(image, -20, 15);
event.setDragDetect(true);
event.consume();
});
struc.get().setOnDragDone(event -> {
stage.getItems().clear();
event.consume();
});
});
return stack.createRegion();
}
}

View file

@ -0,0 +1,47 @@
package io.xpipe.app.browser;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Value;
import org.apache.commons.io.FileUtils;
import java.nio.file.Path;
import java.util.List;
@Value
public class LocalFileTransferStage {
private static final Path TEMP =
FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("download");
@Value
public static class Item {
FileSystem.FileEntry fileEntry;
Path localFile;
BooleanProperty finishedDownload = new SimpleBooleanProperty();
}
ObservableList<Item> items = FXCollections.observableArrayList();
BooleanProperty downloading = new SimpleBooleanProperty();
public void drop(List<FileSystem.FileEntry> entries) {
entries.forEach(entry -> {
Path file = TEMP.resolve(FileNames.getFileName(entry.getPath()));
var item = new Item(entry, file);
items.add(item);
ThreadHelper.runFailableAsync(() -> {
FileUtils.forceMkdirParent(TEMP.toFile());
try (var b = new BusyProperty(downloading)) {
FileSystemHelper.dropFilesInto(FileSystemHelper.getLocal(TEMP),List.of(entry), false);
}
item.finishedDownload.set(true);
});
});
}
}

View file

@ -0,0 +1,28 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.collections.ObservableList;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public class SelectedFileListComp extends SimpleComp {
ObservableList<FileSystem.FileEntry> list;
@Override
protected Region createSimple() {
var c = new ListBoxViewComp<>(list, list, entry -> {
var l = new LabelComp(FileNames.getFileName(entry.getPath())).apply(struc -> struc.get()
.setGraphic(FileIcons.createIcon(entry).createRegion()));
return l;
}).styleClass("selected-file-list");
return c.createRegion();
}
}

View file

@ -2,6 +2,7 @@ package io.xpipe.app.update;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.prefs.AppPrefs;
import javafx.scene.control.Alert;
public class UpdateAvailableAlert {
@ -11,6 +12,12 @@ public class UpdateAvailableAlert {
return;
}
// If we downloaded an update, and decided to no longer automatically update, don't remind us!
// You can still update manually in the about tab
if (!AppPrefs.get().automaticallyUpdate().get()) {
return;
}
if (AppUpdater.get().getDownloadedUpdate().getValue() != null && !AppUpdater.get().isDownloadedUpdateStillLatest()) {
AppUpdater.get().getDownloadedUpdate().setValue(null);
return;

View file

@ -13,6 +13,8 @@ deleteAlertTitle=Confirm deletion
deleteAlertHeader=Do you want to delete the ($COUNT$) selected elements?
selectedElements=Selected elements:
mustNotBeEmpty=$NAME$ must not be empty
download=Drop to download
dragFiles=Drag local files from here
null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$
missingStore=$NAME$ does not exist

View file

@ -1,4 +1,17 @@
/* SPDX-License-Identifier: MIT */
.download-background {
-fx-border-color: -color-neutral-emphasis;
-fx-border-width: 1px 0 0 0;
-fx-padding: 1em;
-fx-background-color: -color-neutral-muted;
}
.selected-file-list {
-fx-padding: 10px;
}
.selected-file-list * {
-fx-spacing: 5px;
}
.browser .bookmark-list {
-fx-border-width: 0 0 1 1;
@ -26,8 +39,8 @@
}
.browser .singular .tab-header-area {
visibility: hidden ;
}
visibility: hidden ;
}
.browser .table-directory-view .table-view {
-color-header-bg: -color-bg-default;