12826ff1cc
Fix OBJ export face directions Menu.removeAction now supports action as argument Fix locking of some element types not getting saved
451 lines
12 KiB
JavaScript
451 lines
12 KiB
JavaScript
const electron = require('@electron/remote');
|
|
const {clipboard, shell, nativeImage, ipcRenderer, dialog} = require('electron');
|
|
const app = electron.app;
|
|
const fs = require('fs');
|
|
const NodeBuffer = require('buffer');
|
|
const zlib = require('zlib');
|
|
const exec = require('child_process').exec;
|
|
const originalFs = require('original-fs');
|
|
const https = require('https');
|
|
const PathModule = require('path');
|
|
|
|
const currentwindow = electron.getCurrentWindow();
|
|
var dialog_win = null,
|
|
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);
|
|
})
|
|
}
|
|
return array
|
|
})();
|
|
|
|
|
|
app.setAppUserModelId('blockbench')
|
|
|
|
|
|
function initializeDesktopApp() {
|
|
|
|
//Setup
|
|
$(document.body).on('click', 'a[href]', (event) => {
|
|
event.preventDefault();
|
|
shell.openExternal(event.target.href);
|
|
return true;
|
|
});
|
|
|
|
function makeUtilFolder(name) {
|
|
let path = PathModule.join(app.getPath('userData'), name)
|
|
if (!fs.existsSync(path)) fs.mkdirSync(path)
|
|
}
|
|
['backups', 'thumbnails'].forEach(makeUtilFolder)
|
|
|
|
createBackup(true)
|
|
|
|
$('.web_only').remove()
|
|
if (__dirname.includes('C:\\xampp\\htdocs\\blockbench')) {
|
|
Blockbench.addFlag('dev')
|
|
}
|
|
|
|
settings.interface_scale.onChange();
|
|
|
|
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()
|
|
}
|
|
|
|
ipcRenderer.send('app-loaded')
|
|
|
|
}
|
|
//Load Model
|
|
function loadOpenWithBlockbenchFile() {
|
|
ipcRenderer.on('open-model', (event, path) => {
|
|
Blockbench.read([path], {}, function(files) {
|
|
files.forEach(file => {
|
|
loadModelFile(file);
|
|
})
|
|
})
|
|
})
|
|
if (electron.process.argv.length >= 2) {
|
|
var extension = pathToExtension(electron.process.argv.last())
|
|
if (Codec.getAllExtensions().includes(extension)) {
|
|
Blockbench.read([electron.process.argv.last()], {}, (files) => {
|
|
loadModelFile(files[0])
|
|
})
|
|
}
|
|
}
|
|
}
|
|
(function() {
|
|
console.log('Electron '+process.versions.electron+', Node '+process.versions.node)
|
|
})()
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
//Recent Projects
|
|
function updateRecentProjects() {
|
|
//Set Local Storage
|
|
localStorage.setItem('recent_projects', JSON.stringify(recent_projects.slice().reverse()))
|
|
}
|
|
function addRecentProject(data) {
|
|
var i = recent_projects.length-1
|
|
while (i >= 0) {
|
|
var p = recent_projects[i]
|
|
if (p.path === data.path) {
|
|
recent_projects.splice(i, 1)
|
|
}
|
|
i--;
|
|
}
|
|
if (data.name.length > 48) data.name = data.name.substr(0, 20) + '...' + data.name.substr(-20);
|
|
let project = {
|
|
name: data.name,
|
|
path: data.path,
|
|
icon: data.icon,
|
|
day: new Date().dayOfYear()
|
|
}
|
|
recent_projects.splice(0, 0, project)
|
|
ipcRenderer.send('add-recent-project', data.path);
|
|
if (recent_projects.length > Math.clamp(settings.recent_projects.value, 0, 256)) {
|
|
recent_projects.pop()
|
|
}
|
|
updateRecentProjects()
|
|
}
|
|
async function updateRecentProjectThumbnail() {
|
|
if (Outliner.elements.length == 0) return;
|
|
let path = Project.export_path || Project.save_path;
|
|
let project = recent_projects.find(p => p.path == path);
|
|
if (!project) return;
|
|
|
|
MediaPreview.resize(180, 100)
|
|
MediaPreview.loadAnglePreset(DefaultCameraPresets[0])
|
|
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)
|
|
|
|
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`)
|
|
Blockbench.writeFile(path, {
|
|
savetype: 'image',
|
|
content: url
|
|
}, resolve)
|
|
let store_path = project.path;
|
|
project.path = '';
|
|
project.path = store_path;
|
|
})
|
|
})
|
|
|
|
// 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)}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
//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'));
|
|
|
|
//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>'+
|
|
(Blockbench.platform == 'win32' ? '<option id="pdn">Paint.NET</option>' : '')+
|
|
'<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;
|
|
if (Blockbench.platform == 'darwin') {
|
|
switch (id) {
|
|
case 'ps': path = '/Applications/Adobe Photoshop 2021/Adobe Photoshop 2021.app'; break;
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
if (id === 'other') {
|
|
selectImageEditorFile(texture)
|
|
|
|
} else if (path) {
|
|
settings.image_editor.value = path
|
|
if (texture) {
|
|
texture.openEditor()
|
|
}
|
|
}
|
|
dialog.hide()
|
|
if (from_settings) {
|
|
Settings.open()
|
|
}
|
|
},
|
|
onCancel() {
|
|
dialog.hide()
|
|
if (from_settings) {
|
|
Settings.open()
|
|
}
|
|
}
|
|
}).show()
|
|
}
|
|
function selectImageEditorFile(texture) {
|
|
let filePaths = electron.dialog.showOpenDialogSync(currentwindow, {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
//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, {
|
|
type: 'info',
|
|
buttons,
|
|
noLink: true,
|
|
title: tl('message.default_textures.title'),
|
|
message: tl('message.default_textures.message'),
|
|
detail
|
|
})
|
|
if (answer === buttons.length-1) {
|
|
return;
|
|
} else if (answer === 0) {
|
|
|
|
let path = Blockbench.pickDirectory({
|
|
title: tl('message.default_textures.select'),
|
|
resource_id: 'texture',
|
|
});
|
|
if (path) {
|
|
settings.default_path.value = path;
|
|
Settings.saveLocalStorages();
|
|
}
|
|
} else {
|
|
settings.default_path.value = false;
|
|
Settings.saveLocalStorages();
|
|
}
|
|
}
|
|
function findExistingFile(paths) {
|
|
for (var path of paths) {
|
|
if (fs.existsSync(path)) {
|
|
return path;
|
|
}
|
|
}
|
|
}
|
|
//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})
|
|
localStorage.setItem('backup_model', model)
|
|
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
|
|
|
|
window.onbeforeunload = function (event) {
|
|
try {
|
|
updateRecentProjectThumbnail()
|
|
} catch(err) {}
|
|
|
|
|
|
if (Blockbench.hasFlag('allow_closing')) {
|
|
try {
|
|
if (!Blockbench.hasFlag('allow_reload')) {
|
|
currentwindow.webContents.closeDevTools()
|
|
}
|
|
} catch (err) {}
|
|
} else {
|
|
setTimeout(async function() {
|
|
let projects = ModelProject.all.slice();
|
|
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;
|
|
}
|
|
}, 2)
|
|
event.returnValue = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function closeBlockbenchWindow() {
|
|
window.onbeforeunload = null;
|
|
Blockbench.addFlag('allow_closing');
|
|
Blockbench.dispatchEvent('before_closing')
|
|
localStorage.removeItem('backup_model')
|
|
if (Project.EditSession) Project.EditSession.quit()
|
|
return currentwindow.close();
|
|
};
|
|
|
|
|
|
ipcRenderer.on('update-available', (event, arg) => {
|
|
console.log('Found new update')
|
|
if (settings.automatic_updates.value) {
|
|
ipcRenderer.send('allow-auto-update');
|
|
|
|
|
|
let icon_node = Blockbench.getIconNode('donut_large');
|
|
icon_node.classList.add('spinning');
|
|
let click_action;
|
|
|
|
let action = new Action('update_status', {
|
|
name: tl('menu.help.updating', [0]),
|
|
icon: icon_node,
|
|
click() {
|
|
if (click_action) click_action()
|
|
}
|
|
})
|
|
action.toElement('#update_menu');
|
|
MenuBar.menus.help.addAction('_');
|
|
MenuBar.menus.help.addAction(action);
|
|
|
|
ipcRenderer.on('update-progress', (event, status) => {
|
|
action.setName(tl('menu.help.updating', [Math.round(status.percent)]));
|
|
})
|
|
ipcRenderer.on('update-error', (event, err) => {
|
|
action.setName(tl('menu.help.update_failed'));
|
|
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';
|
|
click_action = function() {
|
|
Blockbench.showQuickMessage('message.update_after_restart')
|
|
}
|
|
})
|
|
|
|
} else {
|
|
addStartScreenSection({
|
|
color: 'var(--color-back)',
|
|
graphic: {type: 'icon', icon: 'update'},
|
|
text: [
|
|
{type: 'h1', text: tl('message.update_notification.title')},
|
|
{text: tl('message.update_notification.message')},
|
|
{type: 'button', text: tl('generic.enable'), click: (e) => {
|
|
Settings.open({search: 'automatic_updates'})
|
|
}}
|
|
]
|
|
})
|
|
}
|
|
})
|
|
|