mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Implement file browser download window
This commit is contained in:
parent
7fd2e89c77
commit
2763ca40c8
11 changed files with 246 additions and 11 deletions
|
@ -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(
|
||||
|
|
|
@ -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)) {
|
||||
|
|
20
app/src/main/java/io/xpipe/app/browser/FileIcons.java
Normal file
20
app/src/main/java/io/xpipe/app/browser/FileIcons.java
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue