Skip to content

Commit

Permalink
Provide a FileSavePicker implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
besidev committed Oct 22, 2023
1 parent ef9f37b commit 8dcd1f7
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package one.jpro.platform.file.picker;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;

import java.io.File;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
* This is an abstract class that implements the {@link FileSavePicker} interface.
* It provides a base implementation for common functionality shared by native and web implementations.
*
* @author Besmir Beqiri
*/
abstract class BaseFileSavePicker extends BaseFilePicker implements FileSavePicker {

/**
* {@inheritDoc}
*/
BaseFileSavePicker(Node node) {
super(node);
node.setOnMouseClicked(mouseEvent -> showDialog());
}

abstract void showDialog();

// on files selected property
ObjectProperty<Function<File, CompletableFuture<File>>> onFileSelected;

@Override
public final Function<File, CompletableFuture<File>> getOnFileSelected() {
return onFileSelected == null ? null : onFileSelected.get();
}

@Override
public final void setOnFileSelected(Function<File, CompletableFuture<File>> value) {
onFileSelectedProperty().setValue(value);
}

@Override
public final ObjectProperty<Function<File, CompletableFuture<File>>> onFileSelectedProperty() {
if (onFileSelected == null) {
onFileSelected = new SimpleObjectProperty<>(this, "onFileSelected");
}
return onFileSelected;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package one.jpro.platform.file.picker;

import com.jpro.webapi.WebAPI;
import javafx.beans.property.ObjectProperty;
import javafx.scene.Node;

import java.io.File;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
* {@link FilePicker} interface extension for file save operations.
*
* @author Besmir Beqiri
*/
public interface FileSavePicker extends FilePicker {

static FileSavePicker create(Node node) {
if (WebAPI.isBrowser()) {
return new WebFileSavePicker(node);
} else {
return new NativeFileSavePicker(node);
}
}

/**
* Gets the handler to be called when the user selects a file.
*
* @return the event handler or <code>null</code>.
*/
Function<File, CompletableFuture<File>> getOnFileSelected();

/**
* Sets the handler to be called when the user selects a file.
*
* @param value the event handler or <code>null</code>.
*/
void setOnFileSelected(Function<File, CompletableFuture<File>> value);

/**
* Defines the handler to be called when the user selects a file.
* The handler returns the selected files or {@code null} if
* no file has been selected.
*/
ObjectProperty<Function<File, CompletableFuture<File>>> onFileSelectedProperty();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package one.jpro.platform.file.picker;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import one.jpro.platform.file.ExtensionFilter;

import java.io.File;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
* Represents a {@link FileSavePicker} implementation for JavaFX desktop/mobile
* applications. This class specializes for selecting a file to save in the
* native file system.
*
* @author Besmir Beqiri
*/
public class NativeFileSavePicker extends BaseFileSavePicker {

private final FileChooser fileChooser;

public NativeFileSavePicker(Node node) {
super(node);
fileChooser = new FileChooser();
}

@Override
public final String getTitle() {
return fileChooser.getTitle();
}

@Override
public final void setTitle(final String value) {
fileChooser.setTitle(value);
}

@Override
public final StringProperty titleProperty() {
return fileChooser.titleProperty();
}

@Override
public final String getInitialFileName() {
return fileChooser.getInitialFileName();
}

@Override
public final void setInitialFileName(final String value) {
fileChooser.setInitialFileName(value);
}

@Override
public final StringProperty initialFileNameProperty() {
if (initialFileName == null) {
initialFileName = new SimpleStringProperty(this, "initialFileName", getInitialFileName());
fileChooser.initialFileNameProperty().bind(initialFileName);
}
return initialFileName;
}

@Override
public final File getInitialDirectory() {
return fileChooser.getInitialDirectory();
}

@Override
public final void setInitialDirectory(final File value) {
fileChooser.setInitialDirectory(value);
}

@Override
public final ObjectProperty<File> initialDirectoryProperty() {
return fileChooser.initialDirectoryProperty();
}

@Override
public final ObjectProperty<ExtensionFilter> selectedExtensionFilterProperty() {
if (selectedExtensionFilter == null) {
selectedExtensionFilter = new SimpleObjectProperty<>(this, "selectedExtensionFilter") {

@Override
protected void invalidated() {
final ExtensionFilter fileExtension = get();
if (fileExtension != null) {
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(fileExtension.description(),
fileExtension.extensions().stream().map(ext -> "*" + ext).toArray(String[]::new));
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setSelectedExtensionFilter(extFilter);
}
}
};
}
return selectedExtensionFilter;
}

@Override
final void showDialog() {
// Basic configuration
fileChooser.setTitle("Save file as...");

// Retrieve scene and window references from the node
final Scene scene = getNode().getScene();
if (scene == null) {
throw new IllegalStateException("Node must be attached to a scene");
}
final Window window = scene.getWindow();
if (window == null) {
throw new IllegalStateException("Scene must be attached to a stage");
}

// Show save dialog
final File saveToFile = fileChooser.showSaveDialog(window);
if (saveToFile != null) {
final Function<File, CompletableFuture<File>> onFileSelected = getOnFileSelected();
if (onFileSelected != null) {
onFileSelected.apply(saveToFile);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package one.jpro.platform.file.picker;

import com.jpro.webapi.WebAPI;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
import one.jpro.platform.file.ExtensionFilter;
import one.jpro.platform.file.util.SaveUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
* Represents a {@link FileSavePicker} implementation for JavaFX applications
* running on the web via JPro server. This class specializes for downloading
* a file and save it in the native file system.
*
* @author Besmir Beqiri
*/
public class WebFileSavePicker extends BaseFileSavePicker {

private static final Logger logger = LoggerFactory.getLogger(WebFileSavePicker.class);

public WebFileSavePicker(Node node) {
super(node);
}

// title property
private StringProperty title;

@Override
public final String getTitle() {
return (title != null) ? title.get() : null;
}

@Override
public final void setTitle(String value) {
titleProperty().set(value);
}

@Override
public final StringProperty titleProperty() {
if (title == null) {
title = new SimpleStringProperty(this, "title");
}
return title;
}

// initial file name property
@Override
public final String getInitialFileName() {
return (initialFileName == null) ? null : initialFileName.get();
}

@Override
public final void setInitialFileName(String value) {
initialFileNameProperty().set(value);
}

@Override
public final StringProperty initialFileNameProperty() {
if (initialFileName == null) {
initialFileName = new SimpleStringProperty(this, "initialFileName");
}
return initialFileName;
}

// initial directory property
private ObjectProperty<File> initialDirectory;

@Override
public final File getInitialDirectory() {
return (initialDirectory != null) ? initialDirectory.get() : null;
}

@Override
public final void setInitialDirectory(final File value) {
initialDirectoryProperty().set(value);
}

@Override
public final ObjectProperty<File> initialDirectoryProperty() {
if (initialDirectory == null) {
initialDirectory = new SimpleObjectProperty<>(this, "initialDirectory");
}
return initialDirectory;
}

@Override
public final ObjectProperty<ExtensionFilter> selectedExtensionFilterProperty() {
if (selectedExtensionFilter == null) {
selectedExtensionFilter = new SimpleObjectProperty<>(this, "selectedExtensionFilter");
}
return selectedExtensionFilter;
}

@Override
final void showDialog() {
final String fileName = getInitialFileName() == null ? "file_" : getInitialFileName();
final ExtensionFilter fileExtension = getSelectedExtensionFilter();
final String fileType = fileExtension == null ? "" : fileExtension.extensions().get(0);
final Function<File, CompletableFuture<File>> onFileSelected = getOnFileSelected();
if (onFileSelected != null) {
final File tempFile = SaveUtils.createTempFile(fileName, fileType);
onFileSelected.apply(tempFile)
.thenCompose(file -> {
try {
final URL fileUrl = file.toURI().toURL();
final WebAPI webAPI = WebAPI.getWebAPI(getNode().getScene().getWindow());
Platform.runLater(() -> webAPI.downloadURL(fileUrl, file::delete));
return CompletableFuture.completedFuture(file);
} catch (IOException ex) {
return CompletableFuture.failedFuture(ex);
}
}).exceptionallyCompose(ex -> {
if (!tempFile.delete()) {
logger.warn("Could not delete temporary file {}", tempFile.getAbsolutePath());
}
logger.error("Error while downloading file", ex);
return CompletableFuture.failedFuture(ex);
});
}
}
}

0 comments on commit 8dcd1f7

Please sign in to comment.