diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/config/ExportConfig.java b/recaf-ui/src/main/java/software/coley/recaf/ui/config/ExportConfig.java index f18add495..1fd97f1f2 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/config/ExportConfig.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/config/ExportConfig.java @@ -1,5 +1,6 @@ package software.coley.recaf.ui.config; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import software.coley.observables.ObservableBoolean; @@ -23,6 +24,7 @@ public class ExportConfig extends BasicConfigContainer { = new ObservableObject<>(WorkspaceExportOptions.CompressType.MATCH_ORIGINAL); private final ObservableBoolean bundleSupportingResources = new ObservableBoolean(false); private final ObservableBoolean createZipDirEntries = new ObservableBoolean(true); + private final ObservableBoolean warnNoChanges = new ObservableBoolean(true); @Inject public ExportConfig() { @@ -31,11 +33,13 @@ public ExportConfig() { addValue(new BasicConfigValue<>("compression", WorkspaceExportOptions.CompressType.class, compression)); addValue(new BasicConfigValue<>("bundle-supporting-resources", boolean.class, bundleSupportingResources)); addValue(new BasicConfigValue<>("create-zip-dir-entries", boolean.class, createZipDirEntries)); + addValue(new BasicConfigValue<>("warn-no-changes", boolean.class, warnNoChanges)); } /** * @return Compression type to use when exporting workspaces. */ + @Nonnull public ObservableObject getCompression() { return compression; } @@ -43,6 +47,7 @@ public ObservableObject getCompression() { /** * @return {@code true} to include {@link Workspace#getSupportingResources()} in the output. */ + @Nonnull public ObservableBoolean getBundleSupportingResources() { return bundleSupportingResources; } @@ -55,7 +60,16 @@ public ObservableBoolean getBundleSupportingResources() { * * @return {@code true} to create ZIP entries for directory paths. */ + @Nonnull public ObservableBoolean getCreateZipDirEntries() { return createZipDirEntries; } + + /** + * @return {@code true} to warn users when no changes were made prior to exporting. + */ + @Nonnull + public ObservableBoolean getWarnNoChanges() { + return warnNoChanges; + } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java b/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java index 689f625eb..6a72425b8 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java +++ b/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java @@ -3,8 +3,12 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; +import javafx.stage.Stage; +import org.kordamp.ikonli.carbonicons.CarbonIcons; import org.slf4j.Logger; import software.coley.observables.ObservableString; import software.coley.recaf.analytics.logging.Logging; @@ -12,7 +16,9 @@ import software.coley.recaf.services.workspace.WorkspaceManager; import software.coley.recaf.ui.config.ExportConfig; import software.coley.recaf.ui.config.RecentFilesConfig; +import software.coley.recaf.ui.control.FontIconView; import software.coley.recaf.util.ErrorDialogs; +import software.coley.recaf.util.Icons; import software.coley.recaf.util.Lang; import software.coley.recaf.services.workspace.io.WorkspaceExportOptions; import software.coley.recaf.services.workspace.io.WorkspaceExporter; @@ -66,8 +72,21 @@ public void exportCurrent() { * Workspace to export. */ public void export(@Nonnull Workspace workspace) { - // Prompt a path for the user to write to. + // Check if the user hasn't made any changes. Plenty of people have not understood that their changes weren't + // saved for one reason or another (the amount of people seeing a red flash thinking that is fine is crazy) WorkspaceResource primaryResource = workspace.getPrimaryResource(); + boolean noChangesFound = exportConfig.getWarnNoChanges().getValue() && primaryResource.bundleStream() + .allMatch(b -> b.getDirtyKeys().isEmpty()); + if (noChangesFound) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Lang.get("dialog.file.nochanges"), ButtonType.YES, ButtonType.NO); + alert.setTitle(Lang.get("dialog.title.nochanges")); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(Icons.getImage(Icons.LOGO)); + if (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES) + return; + } + + // Prompt a path for the user to write to. ObservableString lastWorkspaceExportDir = recentFilesConfig.getLastWorkspaceExportDirectory(); File lastExportDir = lastWorkspaceExportDir.unboxingMap(File::new); File selectedPath; diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index dceaefca1..b589c99cd 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -135,12 +135,14 @@ dialog.unknownextension=Unknown file extension. Do you want to configure a langu ## File chooser dialog.title.primary=Primary resource dialog.title.supporting=Supporting resources +dialog.title.nochanges=Export without changes? dialog.file.open=Open dialog.file.open.directory=Directories dialog.file.open.file=Files dialog.file.export=Export dialog.file.save=Save dialog.file.nothing=Nothing selected +dialog.file.nochanges=Do you want to export the application even though no changes were made? dialog.filefilter.any=Any type dialog.filefilter.mapping=Mappings dialog.filefilter.input=Applications @@ -666,6 +668,7 @@ service.io.export-config=Exporting service.io.export-config.bundle-supporting-resources=Bundle supporting resources into output service.io.export-config.compression=Compression strategy for contents of output service.io.export-config.create-zip-dir-entries=Create ZIP 'directory' entries in output +service.io.export-config.warn-no-changes=Warn on exporting without any changes made service.io.gson-provider-config=Json service.io.gson-provider-config.pretty-print=Pretty printing service.io.info-importer-config=Content importing