From b754f66003eb322bbf75a161cbf85e2278109879 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 17 Jun 2023 23:41:07 +0200 Subject: [PATCH] Add backup browser --- css/dialogs.css | 60 ++++++++++++++++++++ js/desktop.js | 112 ++++++++++++++++++++++++++++++++++++++ js/interface/actions.js | 8 --- js/interface/interface.js | 2 +- js/interface/menu_bar.js | 2 +- js/util/math_util.js | 16 ++++++ lang/en.json | 6 +- 7 files changed, 194 insertions(+), 12 deletions(-) diff --git a/css/dialogs.css b/css/dialogs.css index aa74c061..1c16f0bf 100644 --- a/css/dialogs.css +++ b/css/dialogs.css @@ -1568,6 +1568,66 @@ dialog#edit_bedrock_binding > .dialog_wrapper > .dialog_content { color: inherit; } +/* View Backups */ + ul#view_backups_list { + max-height: calc(95vh - 220px); + margin-top: 8px; + margin-bottom: 8px; + } + ul#view_backups_list > li { + padding: 2px 6px; + cursor: pointer; + border: 2px solid transparent; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } + ul#view_backups_list > li.current { + border-color: var(--color-accent); + } + ul#view_backups_list > li:hover { + color: var(--color-light); + } + ul#view_backups_list > li.selected { + background-color: var(--color-accent); + color: var(--color-accent_text); + position: relative; + } + #view_backups_list span { + flex-grow: 1; + } + #view_backups_list .view_backups_info_field { + color: var(--color-subtle_text); + width: 90px; + white-space: nowrap; + overflow: hidden; + cursor: inherit; + text-align: right; + } + #view_backups_list .view_backups_info_field:last-child { + width: 82px; + } + ul#view_backups_list > li.selected .view_backups_info_field { + color: inherit; + } + + ol.pagination_numbers { + display: flex; + gap: 3px; + justify-content: center; + } + ol.pagination_numbers > li { + border-radius: 3px; + cursor: pointer; + padding: 0px 7px; + min-width: 22px; + } + ol.pagination_numbers > li:hover, ol.pagination_numbers > li.selected { + background-color: var(--color-accent); + color: var(--color-accent_text); + } + + /* Custom Brush Options */ dialog#brush_options:not(.preset_selected) div.form_bar, dialog#brush_options:not(.preset_selected) hr { diff --git a/js/desktop.js b/js/desktop.js index 10597426..a92ffeed 100644 --- a/js/desktop.js +++ b/js/desktop.js @@ -450,6 +450,118 @@ function createBackup(init) { } }) } + +BARS.defineActions(() => { + + let selected_id; // Remember selected one after re-opening + new Action('view_backups', { + icon: 'fa-archive', + category: 'file', + condition: () => isApp, + click(e) { + + let backup_directory = app.getPath('userData')+osfs+'backups'; + let files = fs.readdirSync(backup_directory); + + let entries = files.map((file, i) => { + let path = PathModule.join(backup_directory, file); + let stats = fs.statSync(path); + + let size = `${separateThousands(Math.round(stats.size / 1024))} KB`; + let entry = { + id: file, + path, + name: file.replace(/backup_\d+\.\d+\.\d+_\d+\.\d+_?/, '').replace(/\.bbmodel$/, '').replace(/_/g, ' ') || 'no name', + date: stats.mtime.toLocaleDateString(), + time: stats.mtime.toLocaleTimeString().replace(/:\d+ /, ' '), + date_long: stats.mtime.toString(), + timestamp: stats.mtime.getTime(), + size, + } + return entry; + }) + entries.sort((a, b) => b.timestamp - a.timestamp); + + let selected; + const dialog = new Dialog({ + id: 'view_backups', + title: 'action.view_backups', + width: 720, + buttons: ['dialog.confirm', 'dialog.view_backups.open_folder', 'dialog.cancel'], + component: { + data() {return { + backups: entries, + page: 0, + per_page: 80, + search_term: '', + selected: (selected_id ? entries.find(e => e.id == selected_id) : null) + }}, + methods: { + select(backup) { + selected = this.selected = backup; + selected_id = backup.id; + }, + open() { + dialog.confirm(); + }, + setPage(number) { + this.page = number; + } + }, + computed: { + filtered_backups() { + let term = this.search_term.toLowerCase(); + return this.backups.filter(backup => { + return backup.name.includes(term); + }) + }, + viewed_backups() { + return this.filtered_backups.slice(this.page * this.per_page, (this.page+1) * this.per_page); + }, + pages() { + let pages = []; + let length = this.filtered_backups.length; + for (let i = 0; i * this.per_page < length; i++) { + pages.push(i); + } + return pages; + } + }, + template: ` +
+
+ +
+ +
    +
  1. {{ number+1 }}
  2. +
+
+ ` + }, + onButton(button) { + if (button == 1) { + shell.openPath(backup_directory); + } + }, + onConfirm() { + Blockbench.read([selected.path], {}, (files) => { + loadModelFile(files[0]); + }) + dialog.close(); + } + }).show(); + } + }) +}) + //Close window.onbeforeunload = function (event) { try { diff --git a/js/interface/actions.js b/js/interface/actions.js index 59fa6b73..29e72999 100644 --- a/js/interface/actions.js +++ b/js/interface/actions.js @@ -1798,14 +1798,6 @@ const BARS = { shell.showItemInFolder(Project.export_path || Project.save_path); } }) - new Action('open_backup_folder', { - icon: 'fa-archive', - category: 'file', - condition: () => isApp, - click: function (e) { - shell.openPath(app.getPath('userData')+osfs+'backups') - } - }) new Action('reload', { icon: 'refresh', category: 'file', diff --git a/js/interface/interface.js b/js/interface/interface.js index fa2262f4..1486d422 100644 --- a/js/interface/interface.js +++ b/js/interface/interface.js @@ -422,7 +422,7 @@ function setupInterface() { Interface.status_bar.menu = new Menu([ 'project_window', 'open_model_folder', - 'open_backup_folder', + 'view_backups', 'save', 'timelapse', 'cancel_gif', diff --git a/js/interface/menu_bar.js b/js/interface/menu_bar.js index 364244e8..62289332 100644 --- a/js/interface/menu_bar.js +++ b/js/interface/menu_bar.js @@ -450,7 +450,7 @@ const MenuBar = { Blockbench.openLink('https://github.com/JannisX11/blockbench/issues'); }}, '_', - 'open_backup_folder', + 'view_backups', '_', {name: 'menu.help.developer', id: 'developer', icon: 'fas.fa-wrench', children: [ 'reload_plugins', diff --git a/js/util/math_util.js b/js/util/math_util.js index e9bfba92..24079f2d 100644 --- a/js/util/math_util.js +++ b/js/util/math_util.js @@ -104,6 +104,22 @@ function trimFloatNumber(val, max_digits = 4) { if (string == -0) return 0; return string; } +function separateThousands(number) { + let str = number.toString(); + let length = str.indexOf('.'); + if (length == -1) length = str.length; + if (length < 4) return str; + + let modified; + for (let i = length; i > 0; i -= 3) { + if (i == length) { + modified = str.substring(i-3); + } else { + modified = str.substring(Math.max(0, i-3), i) + ',' + modified; + } + } + return modified; +} function getAxisLetter(number) { switch (number) { case 0: return 'x'; break; diff --git a/lang/en.json b/lang/en.json index 2b64cebd..436b93a8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -603,6 +603,8 @@ "dialog.input.title": "Input", + "dialog.view_backups.open_folder": "Open Folder", + "dialog.sketchfab_uploader.title": "Upload Sketchfab Model", "dialog.sketchfab_uploader.token": "API Token", "dialog.sketchfab_uploader.about_token": "The token is used to connect Blockbench to your Sketchfab account. You can find it on %0", @@ -1110,8 +1112,8 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.open_model_folder": "Open Model Folder", "action.open_model_folder.desc": "Opens the folder that the model is contained in", - "action.open_backup_folder": "Open Backup Folder", - "action.open_backup_folder.desc": "Opens the Blockbench backup folder", + "action.view_backups": "View Backups", + "action.view_backups.desc": "Browse the list of auto-saved project backups", "action.open_model": "Open Model", "action.open_model.desc": "Open a model file from your computer", "action.open_from_link": "Open Model from Link...",