mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
Finalized backup handling
This commit is contained in:
parent
780220c2d4
commit
97b259d1c2
9 changed files with 164 additions and 35 deletions
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 4ed2b3084d54899e10d10a97eaafe210158768be
|
||||
Subproject commit 4cbffd074bb6d1d5fa13492d19e0736869338ac3
|
|
@ -181,12 +181,12 @@ abstract Save(RawSaveData)
|
|||
|
||||
function get_chartEditorHasBackup():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = [];
|
||||
if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = false;
|
||||
|
||||
return this.optionsChartEditor.hasBackup;
|
||||
}
|
||||
|
||||
function set_chartEditorHasBackup(value:Array<String>):Bool
|
||||
function set_chartEditorHasBackup(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.hasBackup = value;
|
||||
|
|
|
@ -48,7 +48,6 @@ import funkin.data.song.SongData.SongNoteData;
|
|||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.save.Save;
|
||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||
|
@ -1539,8 +1538,12 @@ class ChartEditorState extends HaxeUIState
|
|||
this.error('Failure', 'Failed to load chart (${params.fnfcTargetPath})');
|
||||
|
||||
// Song failed to load, open the Welcome dialog so we aren't in a broken state.
|
||||
this.openWelcomeDialog(false);
|
||||
if (shouldShowBackupAvailableDialog)}
|
||||
var welcomeDialog = this.openWelcomeDialog(false);
|
||||
if (shouldShowBackupAvailableDialog)
|
||||
{
|
||||
this.openBackupAvailableDialog(welcomeDialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (params != null && params.targetSongId != null)
|
||||
{
|
||||
|
@ -1566,7 +1569,6 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
var save:Save = Save.get();
|
||||
|
||||
showBackupAvailablePopup = save.chartEditorHasBackup;
|
||||
if (previousWorkingFilePaths[0] == null)
|
||||
{
|
||||
previousWorkingFilePaths = [null].concat(save.chartEditorPreviousFiles);
|
||||
|
@ -1598,9 +1600,11 @@ class ChartEditorState extends HaxeUIState
|
|||
var filteredWorkingFilePaths:Array<String> = [];
|
||||
for (chartPath in previousWorkingFilePaths)
|
||||
if (chartPath != null) filteredWorkingFilePaths.push(chartPath);
|
||||
|
||||
save.chartEditorHasBackup = hasBackup;
|
||||
save.chartEditorPreviousFiles = filteredWorkingFilePaths;
|
||||
|
||||
if (hasBackup) trace('Queuing backup prompt for next time!');
|
||||
save.chartEditorHasBackup = hasBackup;
|
||||
|
||||
save.chartEditorNoteQuant = noteSnapQuantIndex;
|
||||
save.chartEditorLiveInputStyle = currentLiveInputStyle;
|
||||
save.chartEditorDownscroll = isViewDownscroll;
|
||||
|
@ -2023,7 +2027,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
else
|
||||
{
|
||||
this.exportAllSongData(false);
|
||||
this.exportAllSongData(false, null);
|
||||
}
|
||||
});
|
||||
addUIClickListener('menubarItemSaveChartAs', _ -> this.exportAllSongData());
|
||||
|
@ -2239,10 +2243,12 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function autoSave():Void
|
||||
{
|
||||
var needsAutoSave:Bool = saveDataDirty;
|
||||
|
||||
saveDataDirty = false;
|
||||
|
||||
// Auto-save preferences.
|
||||
writePreferences(false);
|
||||
writePreferences(needsAutoSave);
|
||||
|
||||
// Auto-save the chart.
|
||||
#if html5
|
||||
|
@ -2250,7 +2256,10 @@ class ChartEditorState extends HaxeUIState
|
|||
// TODO: Implement this.
|
||||
#else
|
||||
// Auto-save to temp file.
|
||||
this.exportAllSongData(true);
|
||||
if (needsAutoSave)
|
||||
{
|
||||
this.exportAllSongData(true, null);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
|
@ -2269,7 +2278,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
if (needsAutoSave)
|
||||
{
|
||||
this.exportAllSongData(true);
|
||||
this.exportAllSongData(true, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2286,7 +2295,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
if (needsAutoSave)
|
||||
{
|
||||
this.exportAllSongData(true);
|
||||
this.exportAllSongData(true, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3995,7 +4004,7 @@ class ChartEditorState extends HaxeUIState
|
|||
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
// CTRL + SHIFT + S = Save As
|
||||
this.exportAllSongData(false);
|
||||
this.exportAllSongData(false, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -4006,7 +4015,11 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.S)
|
||||
{
|
||||
this.exportAllSongData(false);
|
||||
this.exportAllSongData(false, null, function(path:String) {
|
||||
// CTRL + SHIFT + S Successful
|
||||
}, function() {
|
||||
// CTRL + SHIFT + S Cancelled
|
||||
});
|
||||
}
|
||||
// CTRL + Q = Quit to Menu
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q)
|
||||
|
|
|
@ -20,6 +20,8 @@ import funkin.util.FileUtil;
|
|||
import funkin.util.SerializerUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.DropDown;
|
||||
|
@ -61,6 +63,7 @@ class ChartEditorDialogHandler
|
|||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
||||
static final CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-variation');
|
||||
static final CHART_EDITOR_DIALOG_ADD_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-difficulty');
|
||||
static final CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT:String = Paths.ui('chart-editor/dialogs/backup-available');
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog giving brief credits for the chart editor.
|
||||
|
@ -246,32 +249,84 @@ class ChartEditorDialogHandler
|
|||
* @param state
|
||||
* @return Null<Dialog>
|
||||
*/
|
||||
public static function openBackupAvailableDialog(state:ChartEditorState):Null<Dialog>
|
||||
public static function openBackupAvailableDialog(state:ChartEditorState, welcomeDialog:Null<Dialog>):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT, true, true);
|
||||
if (dialog == null) throw 'Could not locate Backup Available dialog';
|
||||
dialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// User loaded the backup! Close the welcome dialog behind this.
|
||||
if (welcomeDialog != null) welcomeDialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the dialog, don't close the welcome dialog so we aren't in a broken state.
|
||||
}
|
||||
};
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
|
||||
var backupTimeLabel:Null<Label> = dialog.findComponent('backupTimeLabel', Label);
|
||||
if (backupTimeLabel == null) throw 'Could not locate backupTimeLabel button in Backup Available dialog';
|
||||
|
||||
var latestBackupDate:Null<Date> = ChartEditorImportExportHandler.getLatestBackupDate();
|
||||
if (latestBackupDate != null)
|
||||
{
|
||||
var latestBackupDateStr:String = DateUtil.generateCleanTimestamp(latestBackupDate);
|
||||
backupTimeLabel.text = latestBackupDateStr;
|
||||
}
|
||||
|
||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Backup Available dialog';
|
||||
buttonCancel.onClick = function(_event) {
|
||||
// Don't hide the welcome dialog behind this.
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var buttonGoToFolder:Null<Button> = dialog.findComponent('buttonGoToFolder', Button);
|
||||
if (buttonGoToFolder == null) throw 'Could not locate buttonGoToFolder button in Backup Available dialog';
|
||||
buttonGoToFolder.onClick = function(_event) {
|
||||
// TODO: Is there a way to open a folder and highlight a file in it?
|
||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||
WindowUtil.openFolder(absoluteBackupsPath);
|
||||
// Don't hide the welcome dialog behind this.
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.openBackupFolder();
|
||||
}
|
||||
|
||||
var buttonOpenBackup:Null<Button> = dialog.findComponent('buttonOpenBackup', Button);
|
||||
if (buttonOpenBackup == null) throw 'Could not locate buttonOpenBackup button in Backup Available dialog';
|
||||
buttonOpenBackup.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.openBackup();
|
||||
var latestBackupPath:Null<String> = ChartEditorImportExportHandler.getLatestBackupPath();
|
||||
|
||||
var result:Null<Array<String>> = (latestBackupPath != null) ? state.loadFromFNFCPath(latestBackupPath) : null;
|
||||
if (result != null)
|
||||
{
|
||||
if (result.length == 0)
|
||||
{
|
||||
// No warnings.
|
||||
state.success('Loaded Chart', 'Loaded chart (${latestBackupPath})');
|
||||
}
|
||||
else
|
||||
{
|
||||
// One or more warnings.
|
||||
state.warning('Loaded Chart', 'Loaded chart (${latestBackupPath})\n${result.join("\n")}');
|
||||
}
|
||||
|
||||
// Close the welcome dialog behind this.
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.error('Failed to Load Chart', 'Failed to load chart (${latestBackupPath})');
|
||||
|
||||
// Song failed to load, don't close the Welcome dialog so we aren't in a broken state.
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null<Dialog>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import funkin.util.DateUtil;
|
||||
import haxe.io.Path;
|
||||
import funkin.util.SerializerUtil;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.FileUtil.FileWriteMode;
|
||||
import haxe.io.Bytes;
|
||||
|
@ -22,6 +21,8 @@ import funkin.data.song.importer.ChartManifestData;
|
|||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorImportExportHandler
|
||||
{
|
||||
public static final BACKUPS_PATH:String = './backups/';
|
||||
|
||||
/**
|
||||
* Fetch's a song's existing chart and audio and loads it, replacing the current song.
|
||||
*/
|
||||
|
@ -310,21 +311,56 @@ class ChartEditorImportExportHandler
|
|||
return warnings;
|
||||
}
|
||||
|
||||
public static function getLatestBackupPath()
|
||||
public static function getLatestBackupPath():Null<String>
|
||||
{
|
||||
throw 'Not implemented yet.';
|
||||
#if sys
|
||||
var entries:Array<String> = sys.FileSystem.readDirectory(BACKUPS_PATH);
|
||||
entries.sort(SortUtil.alphabetically);
|
||||
|
||||
var latestBackupPath:Null<String> = entries[(entries.length - 1)];
|
||||
|
||||
if (latestBackupPath == null) return null;
|
||||
return haxe.io.Path.join([BACKUPS_PATH, latestBackupPath]);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
public static function getLatestBackupDate()
|
||||
public static function getLatestBackupDate():Null<Date>
|
||||
{
|
||||
throw 'Not implemented yet.';
|
||||
#if sys
|
||||
var latestBackupPath:Null<String> = getLatestBackupPath();
|
||||
if (latestBackupPath == null) return null;
|
||||
|
||||
var latestBackupName:String = haxe.io.Path.withoutDirectory(latestBackupPath);
|
||||
latestBackupName = haxe.io.Path.withoutExtension(latestBackupName);
|
||||
|
||||
var parts = latestBackupName.split('-');
|
||||
|
||||
// var chart:String = parts[0];
|
||||
// var editor:String = parts[1];
|
||||
var year:Int = Std.parseInt(parts[2] ?? '0') ?? 0;
|
||||
var month:Int = Std.parseInt(parts[3] ?? '1') ?? 1;
|
||||
var day:Int = Std.parseInt(parts[4] ?? '0') ?? 0;
|
||||
var hour:Int = Std.parseInt(parts[5] ?? '0') ?? 0;
|
||||
var minute:Int = Std.parseInt(parts[6] ?? '0') ?? 0;
|
||||
var second:Int = Std.parseInt(parts[7] ?? '0') ?? 0;
|
||||
|
||||
var date:Date = new Date(year, month - 1, day, hour, minute, second);
|
||||
return date;
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* @param force Whether to export without prompting. `false` will prompt the user for a location.
|
||||
* @param targetPath where to export if `force` is `true`. If `null`, will export to the `backups` folder.
|
||||
* @param onSaveCb Callback for when the file is saved.
|
||||
* @param onCancelCb Callback for when saving is cancelled.
|
||||
*/
|
||||
public static function exportAllSongData(state:ChartEditorState, force:Bool = false, ?targetPath:String):Void
|
||||
public static function exportAllSongData(state:ChartEditorState, force:Bool = false, targetPath:Null<String>, ?onSaveCb:String->Void,
|
||||
?onCancelCb:Void->Void):Void
|
||||
{
|
||||
var zipEntries:Array<haxe.zip.Entry> = [];
|
||||
|
||||
|
@ -371,12 +407,13 @@ class ChartEditorImportExportHandler
|
|||
// Force writing to a generic path (autosave or crash recovery)
|
||||
targetMode = Skip;
|
||||
targetPath = Path.join([
|
||||
'./backups/',
|
||||
BACKUPS_PATH,
|
||||
'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
|
||||
]);
|
||||
// We have to force write because the program will die before the save dialog is closed.
|
||||
trace('Force exporting to $targetPath...');
|
||||
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
||||
if (onSaveCb != null) onSaveCb(targetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -384,6 +421,7 @@ class ChartEditorImportExportHandler
|
|||
trace('Force exporting to $targetPath...');
|
||||
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
||||
state.saveDataDirty = false;
|
||||
if (onSaveCb != null) onSaveCb(targetPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -400,11 +438,13 @@ class ChartEditorImportExportHandler
|
|||
trace('Saved to "${paths[0]}"');
|
||||
state.currentWorkingFilePath = paths[0];
|
||||
state.applyWindowTitle();
|
||||
if (onSaveCb != null) onSaveCb(paths[0]);
|
||||
}
|
||||
};
|
||||
|
||||
var onCancel:Void->Void = function() {
|
||||
trace('Export cancelled.');
|
||||
if (onCancelCb != null) onCancelCb();
|
||||
};
|
||||
|
||||
trace('Exporting to user-defined location...');
|
||||
|
|
|
@ -96,7 +96,7 @@ class CLIUtil
|
|||
|
||||
static function printUsage():Void
|
||||
{
|
||||
trace('Usage: Funkin.exe [--chart <chart>]');
|
||||
trace('Usage: Funkin.exe [--chart <chart>] [--help] [--version]');
|
||||
}
|
||||
|
||||
static function buildDefaultParams():CLIParams
|
||||
|
|
|
@ -12,4 +12,11 @@ class DateUtil
|
|||
return
|
||||
'${date.getFullYear()}-${Std.string(date.getMonth() + 1).lpad('0', 2)}-${Std.string(date.getDate()).lpad('0', 2)}-${Std.string(date.getHours()).lpad('0', 2)}-${Std.string(date.getMinutes()).lpad('0', 2)}-${Std.string(date.getSeconds()).lpad('0', 2)}';
|
||||
}
|
||||
|
||||
public static function generateCleanTimestamp(?date:Date = null):String
|
||||
{
|
||||
if (date == null) date = Date.now();
|
||||
|
||||
return '${DateTools.format(date, '%B %d, %Y')} at ${DateTools.format(date, '%I:%M %p')}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,11 +73,6 @@ class FileUtil
|
|||
#end
|
||||
}
|
||||
|
||||
public static function openFolderInExplorer():Void
|
||||
{
|
||||
throw 'Not implemented yet.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Browses for multiple file, then calls `onSelect(paths)` when a path chosen.
|
||||
* Note that on HTML5 this will immediately fail.
|
||||
|
|
|
@ -18,7 +18,7 @@ class WindowUtil
|
|||
* Runs platform-specific code to open a URL in a web browser.
|
||||
* @param targetUrl The URL to open.
|
||||
*/
|
||||
public static function openURL(targetUrl:String)
|
||||
public static function openURL(targetUrl:String):Void
|
||||
{
|
||||
#if CAN_OPEN_LINKS
|
||||
#if linux
|
||||
|
@ -32,6 +32,25 @@ class WindowUtil
|
|||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs platform-specific code to open a path in the file explorer.
|
||||
* @param targetPath The path to open.
|
||||
*/
|
||||
public static function openFolder(targetPath:String):Void
|
||||
{
|
||||
#if CAN_OPEN_LINKS
|
||||
#if windows
|
||||
Sys.command('explorer', [targetPath]);
|
||||
#elseif mac
|
||||
Sys.command('open', [targetPath]);
|
||||
#elseif linux
|
||||
Sys.command('open', [targetPath]);
|
||||
#end
|
||||
#else
|
||||
throw 'Cannot open URLs on this platform.';
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatched when the game window is closed.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue