blockbench/js/desktop.js

507 lines
14 KiB
JavaScript
Raw Normal View History

const electron = require('@electron/remote');
const {clipboard, shell, nativeImage, ipcRenderer, dialog} = require('electron');
2019-07-18 00:02:07 +08:00
const app = electron.app;
const fs = require('fs');
2020-03-05 03:56:17 +08:00
const NodeBuffer = require('buffer');
2019-07-18 00:02:07 +08:00
const zlib = require('zlib');
const exec = require('child_process').exec;
const originalFs = require('original-fs');
const https = require('https');
2020-07-16 15:32:59 +08:00
const PathModule = require('path');
2019-07-18 00:02:07 +08:00
const currentwindow = electron.getCurrentWindow();
var dialog_win = null,
2020-12-22 20:32:49 +08:00
latest_version = false;
const recent_projects = (function() {
let array = [];
var raw = localStorage.getItem('recent_projects')
if (raw) {
try {
array = JSON.parse(raw).slice().reverse()
} catch (err) {}
array = array.filter(project => {
return fs.existsSync(project.path);
})
2020-12-22 20:32:49 +08:00
}
return array
})();
2019-07-18 00:02:07 +08:00
app.setAppUserModelId('blockbench')
2019-09-04 15:37:38 +08:00
2019-12-16 03:04:31 +08:00
function initializeDesktopApp() {
2019-07-18 00:02:07 +08:00
//Setup
$(document.body).on('click', 'a[href]', (event) => {
event.preventDefault();
shell.openExternal(event.target.href);
return true;
});
2020-07-16 15:32:59 +08:00
function makeUtilFolder(name) {
let path = PathModule.join(app.getPath('userData'), name)
if (!fs.existsSync(path)) fs.mkdirSync(path)
2019-07-18 00:02:07 +08:00
}
2020-07-16 15:32:59 +08:00
['backups', 'thumbnails'].forEach(makeUtilFolder)
2019-07-18 00:02:07 +08:00
createBackup(true)
2020-07-16 15:32:59 +08:00
2019-07-18 00:02:07 +08:00
$('.web_only').remove()
if (__dirname.includes('C:\\xampp\\htdocs\\blockbench')) {
Blockbench.addFlag('dev')
}
2021-01-09 21:56:57 +08:00
settings.interface_scale.onChange();
2019-07-18 00:02:07 +08:00
if (Blockbench.platform == 'darwin') {
//Placeholder
$('#mac_window_menu').show()
currentwindow.on('enter-full-screen', () => {
$('#mac_window_menu').hide()
})
currentwindow.on('leave-full-screen', () => {
$('#mac_window_menu').show()
})
} else {
$('#windows_window_menu').show()
}
2021-03-21 03:56:22 +08:00
ipcRenderer.send('app-loaded')
2019-12-16 03:04:31 +08:00
}
//Load Model
function loadOpenWithBlockbenchFile() {
2022-08-06 19:37:20 +08:00
function load(path) {
var extension = pathToExtension(path);
if (extension == 'png') {
Blockbench.read([path], {readtype: 'image'}, (files) => {
loadImages(files);
})
} else if (Codec.getAllExtensions().includes(extension)) {
Blockbench.read([path], {}, (files) => {
loadModelFile(files[0])
})
}
}
2022-08-06 19:37:20 +08:00
ipcRenderer.on('open-model', (event, path) => {
load(path);
})
if (electron.process.argv.length >= 2) {
let path = electron.process.argv.last();
load(path);
}
}
2019-07-18 00:02:07 +08:00
(function() {
console.log('Electron '+process.versions.electron+', Node '+process.versions.node)
})()
2020-11-08 03:09:10 +08:00
window.confirm = function(message, title) {
let index = electron.dialog.showMessageBoxSync(currentwindow, {
title: title || electron.app.name,
detail: message,
type: 'none',
noLink: true,
buttons: [tl('dialog.ok'), tl('dialog.cancel')]
});
return index == 0;
}
window.alert = function(message, title) {
electron.dialog.showMessageBoxSync(electron.getCurrentWindow(), {
title: title || electron.app.name,
detail: message
});
}
2020-11-08 03:09:10 +08:00
2019-07-18 00:02:07 +08:00
//Recent Projects
function updateRecentProjects() {
2022-07-02 02:38:21 +08:00
recent_projects.splice(Math.clamp(settings.recent_projects.value, 0, 512));
let fav_count = 0;
recent_projects.forEach((project, i) => {
if (project.favorite) {
recent_projects.splice(i, 1);
recent_projects.splice(fav_count, 0, project);
fav_count++;
}
})
2019-07-18 00:02:07 +08:00
//Set Local Storage
2022-07-02 02:38:21 +08:00
localStorage.setItem('recent_projects', JSON.stringify(recent_projects.slice().reverse()));
2019-07-18 00:02:07 +08:00
}
function addRecentProject(data) {
2022-07-02 02:38:21 +08:00
var i = recent_projects.length-1;
let former_entry;
2019-07-18 00:02:07 +08:00
while (i >= 0) {
var p = recent_projects[i]
if (p.path === data.path) {
2022-07-02 02:38:21 +08:00
recent_projects.splice(i, 1);
former_entry = p;
2019-07-18 00:02:07 +08:00
}
i--;
}
2020-04-26 02:25:07 +08:00
if (data.name.length > 48) data.name = data.name.substr(0, 20) + '...' + data.name.substr(-20);
2020-03-05 03:56:17 +08:00
let project = {
2019-07-18 00:02:07 +08:00
name: data.name,
path: data.path,
icon: data.icon,
favorite: former_entry ? former_entry.favorite : false,
day: new Date().dayOfYear(),
2020-03-05 03:56:17 +08:00
}
recent_projects.splice(0, 0, project)
ipcRenderer.send('add-recent-project', data.path);
2022-08-06 20:34:55 +08:00
StartScreen.vue.updateThumbnails([data.path]);
2019-07-18 00:02:07 +08:00
updateRecentProjects()
}
function updateRecentProjectData() {
let project = Project.getProjectMemory();
if (!project) return;
project.name = Project.name;
project.textures = Texture.all.filter(t => t.path).map(t => t.path);
if (Format.animation_files) {
project.animation_files = [];
Animation.all.forEach(anim => {
if (anim.path) project.animation_files.safePush(anim.path);
})
}
Blockbench.dispatchEvent('update_recent_project_data', {data: project});
updateRecentProjects()
}
2021-05-05 19:54:41 +08:00
async function updateRecentProjectThumbnail() {
if (Outliner.elements.length == 0) return;
let project = Project.getProjectMemory();
2020-07-16 15:32:59 +08:00
if (!project) return;
MediaPreview.resize(180, 100)
MediaPreview.loadAnglePreset(DefaultCameraPresets[0])
MediaPreview.setFOV(30);
2020-07-16 15:32:59 +08:00
let center = getSelectionCenter(true);
MediaPreview.controls.target.fromArray(center);
MediaPreview.controls.target.add(scene.position);
let box = Canvas.getModelSize();
let size = Math.max(box[0], box[1]*2)
MediaPreview.camera.position.multiplyScalar(size/50)
let thumbnail;
2021-05-05 19:54:41 +08:00
await new Promise((resolve, reject) => {
MediaPreview.screenshot({crop: false}, url => {
let hash = project.path.hashCode().toString().replace(/^-/, '0');
let path = PathModule.join(app.getPath('userData'), 'thumbnails', `${hash}.png`)
thumbnail = url;
2021-05-05 19:54:41 +08:00
Blockbench.writeFile(path, {
savetype: 'image',
content: url
}, resolve)
let store_path = project.path;
project.path = '';
project.path = store_path;
2020-07-16 15:32:59 +08:00
})
})
Blockbench.dispatchEvent('update_recent_project_thumbnail', {data: project, thumbnail});
2022-08-06 20:34:55 +08:00
StartScreen.vue.updateThumbnails([project.path]);
2020-07-16 15:32:59 +08:00
// Clean old files
if (Math.random() < 0.2) {
let folder_path = PathModule.join(app.getPath('userData'), 'thumbnails')
let existing_names = [];
recent_projects.forEach(project => {
let hash = project.path.hashCode().toString().replace(/^-/, '0');
existing_names.safePush(hash)
})
fs.readdir(folder_path, (err, files) => {
if (!err) {
files.forEach((name, i) => {
if (existing_names.includes(name.replace(/\..+$/, '')) == false) {
try {
fs.unlinkSync(folder_path +osfs+ name)
} catch (err) {console.log(err)}
}
})
}
})
}
}
function loadDataFromModelMemory() {
let project = Project.getProjectMemory();
if (!project) return;
if (project.textures) {
Blockbench.read(project.textures, {}, files => {
files.forEach(f => {
new Texture({name: f.name}).fromFile(f).add(false).fillParticle()
})
})
}
if (project.animation_files && Format.animation_files) {
Blockbench.read(project.animation_files, {}, files => {
files.forEach(file => {
Animator.importFile(file);
})
})
}
Blockbench.dispatchEvent('load_from_recent_project_data', {data: project});
}
2019-07-18 00:02:07 +08:00
2019-12-16 03:04:31 +08:00
//Window Controls
function updateWindowState(e, type) {
$('#header_free_bar').toggleClass('resize_space', !currentwindow.isMaximized());
}
currentwindow.on('maximize', e => updateWindowState(e, 'maximize'));
currentwindow.on('unmaximize', e => updateWindowState(e, 'unmaximize'));
currentwindow.on('enter-full-screen', e => updateWindowState(e, 'screen'));
currentwindow.on('leave-full-screen', e => updateWindowState(e, 'screen'));
currentwindow.on('ready-to-show', e => updateWindowState(e, 'load'));
2019-07-18 00:02:07 +08:00
//Image Editor
function changeImageEditor(texture, from_settings) {
var dialog = new Dialog({
title: tl('message.image_editor.title'),
id: 'image_editor',
lines: ['<div class="dialog_bar"><select class="input_wide">'+
'<option id="ps">Photoshop</option>'+
'<option id="gimp">Gimp</option>'+
2019-12-16 03:04:31 +08:00
(Blockbench.platform == 'win32' ? '<option id="pdn">Paint.NET</option>' : '')+
2019-07-18 00:02:07 +08:00
'<option id="other">'+tl('message.image_editor.file')+'</option>'+
'</select></div>'],
draggable: true,
onConfirm() {
var id = $('.dialog#image_editor option:selected').attr('id')
var path;
2019-12-16 03:04:31 +08:00
if (Blockbench.platform == 'darwin') {
switch (id) {
case 'ps': path = '/Applications/Adobe Photoshop 2021/Adobe Photoshop 2021.app'; break;
2019-12-16 03:04:31 +08:00
case 'gimp':path = '/Applications/Gimp-2.10.app'; break;
}
} else {
switch (id) {
case 'ps': path = 'C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe'; break;
2019-12-16 03:04:31 +08:00
case 'gimp':path = 'C:\\Program Files\\GIMP 2\\bin\\gimp-2.10.exe'; break;
case 'pdn': path = 'C:\\Program Files\\paint.net\\PaintDotNet.exe'; break;
}
2019-07-18 00:02:07 +08:00
}
if (id === 'other') {
selectImageEditorFile(texture)
} else if (path) {
settings.image_editor.value = path
if (texture) {
texture.openEditor()
}
}
dialog.hide()
if (from_settings) {
2022-03-06 05:59:54 +08:00
BarItems.settings_window.click()
2019-07-18 00:02:07 +08:00
}
},
onCancel() {
dialog.hide()
if (from_settings) {
2022-03-06 05:59:54 +08:00
BarItems.settings_window.click()
2019-07-18 00:02:07 +08:00
}
}
}).show()
}
function selectImageEditorFile(texture) {
let filePaths = electron.dialog.showOpenDialogSync(currentwindow, {
2019-07-18 00:02:07 +08:00
title: tl('message.image_editor.exe'),
filters: [{name: 'Executable Program', extensions: ['exe', 'app']}]
})
if (filePaths) {
settings.image_editor.value = filePaths[0]
if (texture) {
texture.openEditor();
}
}
2019-07-18 00:02:07 +08:00
}
//Default Pack
function openDefaultTexturePath() {
let detail = tl('message.default_textures.detail');
if (settings.default_path.value) {
detail += '\n\n' + tl('message.default_textures.current') + ': ' + settings.default_path.value;
}
let buttons = (
settings.default_path.value ? [tl('dialog.continue'), tl('generic.remove'), tl('dialog.cancel')]
: [tl('dialog.continue'), tl('dialog.cancel')]
)
var answer = electron.dialog.showMessageBoxSync(currentwindow, {
2019-07-18 00:02:07 +08:00
type: 'info',
buttons,
2019-07-18 00:02:07 +08:00
noLink: true,
title: tl('message.default_textures.title'),
message: tl('message.default_textures.message'),
detail
2019-07-18 00:02:07 +08:00
})
if (answer === buttons.length-1) {
2019-07-18 00:02:07 +08:00
return;
} else if (answer === 0) {
2021-01-09 21:49:12 +08:00
let path = Blockbench.pickDirectory({
2019-07-18 00:02:07 +08:00
title: tl('message.default_textures.select'),
2021-01-09 21:49:12 +08:00
resource_id: 'texture',
});
if (path) {
settings.default_path.value = path;
Settings.saveLocalStorages();
2021-01-09 21:49:12 +08:00
}
2019-07-18 00:02:07 +08:00
} else {
settings.default_path.value = false;
Settings.saveLocalStorages();
2019-07-18 00:02:07 +08:00
}
}
2019-12-16 03:04:31 +08:00
function findExistingFile(paths) {
for (var path of paths) {
if (fs.existsSync(path)) {
return path;
}
}
}
2019-07-18 00:02:07 +08:00
//Backup
function createBackup(init) {
setTimeout(createBackup, limitNumber(parseFloat(settings.backup_interval.value), 1, 10e8)*60000)
var duration = parseInt(settings.backup_retain.value)+1
var folder_path = app.getPath('userData')+osfs+'backups'
var d = new Date()
var days = d.getDate() + (d.getMonth()+1)*30.44 + (d.getYear()-100)*365.25
if (init) {
//Clear old backups
fs.readdir(folder_path, (err, files) => {
if (!err) {
files.forEach((name, i) => {
var date = name.split('_')[1]
if (date) {
var nums = date.split('.')
nums.forEach((n, ni) => {
nums[ni] = parseInt(n)
})
var b_days = nums[0] + nums[1]*30.44 + nums[2]*365.25
if (!isNaN(b_days) && days - b_days > duration) {
try {
fs.unlinkSync(folder_path +osfs+ name)
} catch (err) {console.log(err)}
}
}
})
}
})
}
if (init || elements.length === 0) return;
var model = Codecs.project.compile({compressed: true, backup: true})
2019-07-18 00:02:07 +08:00
var file_name = 'backup_'+d.getDate()+'.'+(d.getMonth()+1)+'.'+(d.getYear()-100)+'_'+d.getHours()+'.'+d.getMinutes()
var file_path = folder_path+osfs+file_name+'.bbmodel'
fs.writeFile(file_path, model, function (err) {
if (err) {
console.log('Error creating backup: '+err)
}
})
}
//Close
2020-07-16 15:32:59 +08:00
window.onbeforeunload = function (event) {
2020-07-16 15:32:59 +08:00
try {
updateRecentProjectThumbnail()
updateRecentProjectData()
2020-07-16 15:32:59 +08:00
} catch(err) {}
2021-05-30 16:48:10 +08:00
if (Blockbench.hasFlag('allow_closing')) {
try {
if (!Blockbench.hasFlag('allow_reload')) {
currentwindow.webContents.closeDevTools()
}
2021-05-30 16:48:10 +08:00
} catch (err) {}
} else {
setTimeout(async function() {
let projects = ModelProject.all.slice();
if (projects[0]) await projects[0].select();
for (let project of projects) {
let closed = await project.close();
if (!closed) return false;
}
if (ModelProject.all.length === 0) {
closeBlockbenchWindow()
return true;
} else {
return false;
}
2019-07-18 00:02:07 +08:00
}, 2)
event.returnValue = true;
2019-07-18 00:02:07 +08:00
return true;
}
}
2020-07-16 15:32:59 +08:00
2019-07-18 00:02:07 +08:00
function closeBlockbenchWindow() {
window.onbeforeunload = null;
2019-07-18 00:02:07 +08:00
Blockbench.addFlag('allow_closing');
Blockbench.dispatchEvent('before_closing')
if (Project.EditSession) Project.EditSession.quit()
2019-08-18 00:26:14 +08:00
return currentwindow.close();
2020-07-16 15:32:59 +08:00
};
2021-03-21 03:56:22 +08:00
ipcRenderer.on('update-available', (event, arg) => {
console.log('Found new update:', arg.version)
2021-03-21 03:56:22 +08:00
if (settings.automatic_updates.value) {
ipcRenderer.send('allow-auto-update');
2020-07-16 15:32:59 +08:00
2021-03-21 03:56:22 +08:00
let icon_node = Blockbench.getIconNode('donut_large');
icon_node.classList.add('spinning');
let click_action;
let action = new Action('update_status', {
2021-03-21 03:56:22 +08:00
name: tl('menu.help.updating', [0]),
icon: icon_node,
click() {
if (click_action) click_action()
2020-07-16 15:32:59 +08:00
}
})
action.toElement('#update_menu');
2021-03-21 03:56:22 +08:00
MenuBar.menus.help.addAction('_');
MenuBar.menus.help.addAction(action);
2020-07-16 15:32:59 +08:00
2021-03-21 03:56:22 +08:00
ipcRenderer.on('update-progress', (event, status) => {
action.setName(tl('menu.help.updating', [Math.round(status.percent)]));
2021-03-21 03:56:22 +08:00
})
ipcRenderer.on('update-error', (event, err) => {
action.setName(tl('menu.help.update_failed'));
2021-03-21 03:56:22 +08:00
icon_node.textContent = 'warning';
icon_node.classList.remove('spinning')
click_action = function() {
currentwindow.openDevTools()
}
console.error(err);
})
ipcRenderer.on('update-downloaded', (event) => {
action.setName(tl('message.update_after_restart'));
MenuBar.menus.help.removeAction(action);
icon_node.textContent = 'done';
icon_node.classList.remove('spinning');
icon_node.style.color = '#5ef570';
2021-03-21 03:56:22 +08:00
click_action = function() {
Blockbench.showQuickMessage('message.update_after_restart')
2021-03-21 03:56:22 +08:00
}
})
2020-07-16 15:32:59 +08:00
2021-03-21 03:56:22 +08:00
} else {
addStartScreenSection({
color: 'var(--color-back)',
graphic: {type: 'icon', icon: 'update'},
text: [
{type: 'h2', text: tl('message.update_notification.title')},
2021-03-21 03:56:22 +08:00
{text: tl('message.update_notification.message')},
{type: 'button', text: tl('generic.enable'), click: (e) => {
settings.automatic_updates.set(true);
2021-03-21 03:56:22 +08:00
}}
]
})
}
})