-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide a
FileSavePicker
implementation
- Loading branch information
Showing
4 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
jpro-file/src/main/java/one/jpro/platform/file/picker/BaseFileSavePicker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
jpro-file/src/main/java/one/jpro/platform/file/picker/FileSavePicker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
126 changes: 126 additions & 0 deletions
126
jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileSavePicker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
132 changes: 132 additions & 0 deletions
132
jpro-file/src/main/java/one/jpro/platform/file/picker/WebFileSavePicker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
} | ||
} | ||
} |