Setup vite electron

This commit is contained in:
JannisX11 2025-03-02 15:06:36 +01:00
parent 22eba0c062
commit c9426c4465
34 changed files with 1346 additions and 2719 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
/dist/
/dist-vite/
/dist-electron/
index.php
electron-builder.env
node_modules/

23
.vscode/debug.script.mjs vendored Normal file
View File

@ -0,0 +1,23 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { spawn } from 'node:child_process'
const pkg = createRequire(import.meta.url)('../package.json')
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// write .debug.env
const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`)
fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n'))
// bootstrap
spawn(
// TODO: terminate `npm run dev` when Debug exits.
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'dev'],
{
stdio: 'inherit',
env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }),
},
)

53
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,53 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"compounds": [
{
"name": "Debug App",
"preLaunchTask": "Before Debug",
"configurations": [
"Debug Main Process",
"Debug Renderer Process"
],
"presentation": {
"hidden": false,
"group": "",
"order": 1
},
"stopAll": true
}
],
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": [
"--remote-debugging-port=9229",
"."
],
"envFile": "${workspaceFolder}/.vscode/.debug.env",
"console": "integratedTerminal"
},
{
"name": "Debug Renderer Process",
"port": 9229,
"request": "attach",
"type": "chrome",
"timeout": 60000,
"skipFiles": [
"<node_internals>/**",
"${workspaceRoot}/node_modules/**",
"${workspaceRoot}/dist-electron/**",
// Skip files in host(VITE_DEV_SERVER_URL)
"http://127.0.0.1:3344/**"
]
},
]
}

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.tsc.autoDetect": "off",
"json.schemas": [
{
"fileMatch": [
"/*electron-builder.json5",
"/*electron-builder.json"
],
"url": "https://json.schemastore.org/electron-builder"
}
]
}

24
.vscode/tasks.json vendored
View File

@ -10,6 +10,30 @@
"kind": "build",
"isDefault": true
}
},
{
"label": "Before Debug",
"type": "shell",
"command": "node .vscode/.debug.script.mjs",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"fileLocation": "relative",
"pattern": {
// TODO: correct "regexp"
"regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$",
"file": 1,
"line": 3,
"column": 4,
"code": 5,
"message": 6
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*VITE v.* ready in \\d* ms.*$",
"endsPattern": "^.*\\[startup\\] Electron App.*$"
}
}
}
]
}

23
electron/electron-env.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
/// <reference types="vite-plugin-electron/electron-env" />
declare namespace NodeJS {
interface ProcessEnv {
VSCODE_DEBUG?: 'true'
/**
* The built directory structure
*
* ```tree
* dist-electron
* main
* index.js > Electron-Main
* preload
* index.mjs > Preload-Scripts
* dist
* index.html > Electron-Renderer
* ```
*/
APP_ROOT: string
/** /dist/ or /public/ */
VITE_PUBLIC: string
}
}

View File

@ -1,315 +1,364 @@
const {app, BrowserWindow, Menu, ipcMain, shell} = require('electron')
const path = require('path')
const url = require('url')
const { autoUpdater } = require('electron-updater');
const fs = require('fs');
const {getColorHexRGB} = require('electron-color-picker')
require('@electron/remote/main').initialize()
let orig_win;
let all_wins = [];
let load_project_data;
(() => {
// Allow advanced users to specify a custom userData directory.
// Useful for portable installations, and for setting up development environments.
const index = process.argv.findIndex(arg => arg === '--userData');
if (index !== -1) {
if (!process.argv.at(index + 1)) {
console.error('No path specified after --userData')
process.exit(1)
}
app.setPath('userData', process.argv[index + 1]);
}
})()
const LaunchSettings = {
path: path.join(app.getPath('userData'), 'launch_settings.json'),
settings: {},
get(key) {
return this.settings[key]
},
set(key, value) {
this.settings[key] = value;
let content = JSON.stringify(this.settings, null, '\t');
fs.writeFileSync(this.path, content);
},
load() {
try {
if (fs.existsSync(this.path)) {
let content = fs.readFileSync(this.path, 'utf-8');
this.settings = JSON.parse(content);
}
} catch (error) {}
return this;
}
}.load();
if (LaunchSettings.get('hardware_acceleration') == false) {
app.disableHardwareAcceleration();
}
function createWindow(second_instance, options = {}) {
if (app.requestSingleInstanceLock && !app.requestSingleInstanceLock()) {
app.quit()
return;
}
let win_options = {
icon: 'icon.ico',
show: false,
backgroundColor: '#21252b',
frame: LaunchSettings.get('native_window_frame') === true,
titleBarStyle: 'hidden',
minWidth: 640,
minHeight: 480,
width: 1080,
height: 720,
webPreferences: {
webgl: true,
webSecurity: true,
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
}
};
if (options.position) {
win_options.x = options.position[0] - 300;
win_options.y = Math.max(options.position[1] - 100, 0);
}
let win = new BrowserWindow(win_options)
if (!orig_win) orig_win = win;
all_wins.push(win);
require('@electron/remote/main').enable(win.webContents)
var index_path = path.join(__dirname, 'index.html')
if (process.platform === 'darwin') {
let template = [
{
"label": "Blockbench",
"submenu": [
{
"role": "hide"
},
{
"role": "hideothers"
},
{
"role": "unhide"
},
{
"type": "separator"
},
{
"role": "quit"
}
]
},
{
"label": "Edit",
"submenu": [
{
"role": "cut"
},
{
"role": "copy"
},
{
"role": "paste"
},
{
"role": "selectall"
}
]
},
{
"label": "Window",
"role": "window",
"submenu": [
{
"label": "Toggle Full Screen",
"accelerator": "Ctrl+Command+F"
},
{
"role": "minimize"
},
{
"role": "close"
},
{
"type": "separator"
},
{
"role": "front"
}
]
}
]
var osxMenu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(osxMenu)
} else {
win.setMenu(null);
}
if (options.maximize !== false) win.maximize()
win.show()
win.loadURL(url.format({
pathname: index_path,
protocol: 'file:',
slashes: true
}))
win.webContents.openDevTools()
win.on('closed', () => {
win = null;
all_wins.splice(all_wins.indexOf(win), 1);
})
if (second_instance === true) {
win.webContents.second_instance = true;
}
return win;
}
app.commandLine.appendSwitch('ignore-gpu-blacklist')
app.commandLine.appendSwitch('ignore-gpu-blocklist')
app.commandLine.appendSwitch('enable-accelerated-video')
app.on('second-instance', function (event, argv, cwd) {
process.argv = argv;
let win = all_wins.find(win => !win.isDestroyed());
if (win && argv[argv.length-1 || 1] && argv[argv.length-1 || 1].substr(0, 2) !== '--') {
win.webContents.send('open-model', argv[argv.length-1 || 1]);
win.focus();
} else {
createWindow(true);
}
})
app.on('open-file', function (event, path) {
process.argv[process.argv.length-1 || 1] = path;
let win = all_wins.find(win => !win.isDestroyed());
if (win) {
win.webContents.send('open-model', path);
}
})
ipcMain.on('edit-launch-setting', (event, arg) => {
LaunchSettings.set(arg.key, arg.value);
})
ipcMain.on('add-recent-project', (event, path) => {
app.addRecentDocument(path);
})
ipcMain.on('dragging-tab', (event, value) => {
all_wins.forEach(win => {
if (win.isDestroyed() || win.id == event.sender.id) return;
win.webContents.send('accept-detached-tab', JSON.parse(value));
})
})
ipcMain.on('new-window', (event, data, position) => {
if (typeof data == 'string') load_project_data = JSON.parse(data);
if (position) {
position = JSON.parse(position)
let place_in_window = all_wins.find(win => {
if (win.isDestroyed() || win.webContents == event.sender || win.isMinimized()) return false;
let pos = win.getPosition();
let size = win.getSize();
return (position.offset[0] >= pos[0] && position.offset[0] <= pos[0] + size[0]
&& position.offset[1] >= pos[1] && position.offset[1] <= pos[1] + size[1]);
})
if (place_in_window) {
place_in_window.send('load-tab', load_project_data);
place_in_window.focus();
load_project_data = null;
} else {
createWindow(true, {
maximize: false,
position: position.offset
});
}
} else {
createWindow(true);
}
})
ipcMain.on('close-detached-project', async (event, window_id, uuid) => {
let window = all_wins.find(win => win.id == window_id);
if (window) window.send('close-detached-project', uuid);
})
ipcMain.on('request-color-picker', async (event, arg) => {
const color = await getColorHexRGB().catch((error) => {
console.warn('[Error] Failed to pick color', error)
return ''
})
if (color) {
all_wins.forEach(win => {
if (win.isDestroyed() || (!arg.sync && win.webContents.getProcessId() != event.sender.getProcessId())) return;
win.webContents.send('set-main-color', color)
})
}
})
ipcMain.on('show-item-in-folder', async (event, path) => {
shell.showItemInFolder(path);
})
ipcMain.on('open-in-default-app', async (event, path) => {
shell.openPath(path);
})
app.on('ready', () => {
createWindow()
let app_was_loaded = false;
ipcMain.on('app-loaded', () => {
if (load_project_data) {
all_wins[all_wins.length-1].send('load-tab', load_project_data);
load_project_data = null;
}
if (app_was_loaded) {
console.log('[Blockbench] App reloaded or new window opened')
return;
}
app_was_loaded = true;
if (process.execPath && process.execPath.match(/node_modules[\\\/]electron/)) {
console.log('[Blockbench] App launched in development mode')
} else {
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.autoDownload = false;
if (LaunchSettings.get('update_to_prereleases') === true) {
autoUpdater.allowPrerelease = true;
//autoUpdater.channel = 'beta';
}
autoUpdater.on('update-available', (a) => {
console.log('update-available', a)
ipcMain.on('allow-auto-update', () => {
autoUpdater.downloadUpdate()
})
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-available', a);
})
autoUpdater.on('update-downloaded', (a) => {
console.log('update-downloaded', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-downloaded', a)
})
autoUpdater.on('error', (a) => {
console.log('update-error', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-error', a)
})
autoUpdater.on('download-progress', (a) => {
console.log('update-progress', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-progress', a)
})
autoUpdater.checkForUpdates().catch(err => {})
}
})
})
app.on('window-all-closed', () => {
app.quit()
})
import {app, BrowserWindow, Menu, ipcMain, shell} from 'electron'
import path from 'path'
import url from 'url'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import fs from 'node:fs'
import {getColorHexRGB} from 'electron-color-picker'
const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const { autoUpdater } = require('electron-updater');
const remote = require('@electron/remote/main')
remote.initialize();
let all_wins = [];
let orig_win;
let load_project_data;
(() => {
// Allow advanced users to specify a custom userData directory.
// Useful for portable installations, and for setting up development environments.
const index = process.argv.findIndex(arg => arg === '--userData');
if (index !== -1) {
if (!process.argv.at(index + 1)) {
console.error('No path specified after --userData')
process.exit(1)
}
app.setPath('userData', process.argv[index + 1]);
}
})()
process.env.APP_ROOT = path.join(__dirname, '../..')
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist-vite')
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
const preload = path.join(__dirname, '../preload/index.mjs')
const indexHtml = path.join(RENDERER_DIST, 'index.html')
const LaunchSettings = {
path: path.join(app.getPath('userData'), 'launch_settings.json'),
settings: {},
get(key) {
return this.settings[key]
},
set(key, value) {
this.settings[key] = value;
let content = JSON.stringify(this.settings, null, '\t');
fs.writeFileSync(this.path, content);
},
load() {
try {
if (fs.existsSync(this.path)) {
let content = fs.readFileSync(this.path, 'utf-8');
this.settings = JSON.parse(content);
}
} catch (error) {}
return this;
}
}.load();
if (LaunchSettings.get('hardware_acceleration') == false) {
app.disableHardwareAcceleration();
}
function createWindow(second_instance, options = {}) {
if (app.requestSingleInstanceLock && !app.requestSingleInstanceLock()) {
app.quit()
return;
}
let win_options = {
icon: 'icon.ico',
show: false,
backgroundColor: '#21252b',
frame: LaunchSettings.get('native_window_frame') === true,
titleBarStyle: 'hidden',
minWidth: 640,
minHeight: 480,
width: 1080,
height: 720,
webPreferences: {
webgl: true,
webSecurity: true,
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
}
};
if (options.position) {
win_options.x = options.position[0] - 300;
win_options.y = Math.max(options.position[1] - 100, 0);
}
let win = new BrowserWindow(win_options)
if (!orig_win) orig_win = win;
all_wins.push(win);
remote.enable(win.webContents)
if (process.platform === 'darwin') {
let template = [
{
"label": "Blockbench",
"submenu": [
{
"role": "hide"
},
{
"role": "hideothers"
},
{
"role": "unhide"
},
{
"type": "separator"
},
{
"role": "quit"
}
]
},
{
"label": "Edit",
"submenu": [
{
"role": "cut"
},
{
"role": "copy"
},
{
"role": "paste"
},
{
"role": "selectall"
}
]
},
{
"label": "Window",
"role": "window",
"submenu": [
{
"label": "Toggle Full Screen",
"accelerator": "Ctrl+Command+F"
},
{
"role": "minimize"
},
{
"role": "close"
},
{
"type": "separator"
},
{
"role": "front"
}
]
}
]
var osxMenu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(osxMenu)
} else {
win.setMenu(null);
}
if (options.maximize !== false) win.maximize()
win.show()
if (VITE_DEV_SERVER_URL) { // #298
win.loadURL(VITE_DEV_SERVER_URL)
// Open devTool if the app is not packaged
win.webContents.openDevTools()
} else {
win.loadFile(indexHtml)
}
// Test actively push message to the Electron-Renderer
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', new Date().toLocaleString())
})
// Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https:')) shell.openExternal(url)
return { action: 'deny' }
})
/*win.webContents.session.webRequest.onBeforeSendHeaders(
(details, callback) => {
callback({ requestHeaders: { Origin: '*', ...details.requestHeaders } });
},
);
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
'Access-Control-Allow-Origin': ['*'],
...details.responseHeaders,
},
});
});*/
win.webContents.openDevTools()
win.on('closed', () => {
win = null;
all_wins.splice(all_wins.indexOf(win), 1);
})
if (second_instance === true) {
win.webContents.second_instance = true;
}
return win;
}
app.commandLine.appendSwitch('ignore-gpu-blacklist')
app.commandLine.appendSwitch('ignore-gpu-blocklist')
app.commandLine.appendSwitch('enable-accelerated-video')
app.on('second-instance', function (event, argv, cwd) {
process.argv = argv;
let win = all_wins.find(win => !win.isDestroyed());
if (win && argv[argv.length-1 || 1] && argv[argv.length-1 || 1].substr(0, 2) !== '--') {
win.webContents.send('open-model', argv[argv.length-1 || 1]);
win.focus();
} else {
createWindow(true);
}
})
app.on('open-file', function (event, path) {
process.argv[process.argv.length-1 || 1] = path;
let win = all_wins.find(win => !win.isDestroyed());
if (win) {
win.webContents.send('open-model', path);
}
})
ipcMain.on('edit-launch-setting', (event, arg) => {
LaunchSettings.set(arg.key, arg.value);
})
ipcMain.on('add-recent-project', (event, path) => {
app.addRecentDocument(path);
})
ipcMain.on('dragging-tab', (event, value) => {
all_wins.forEach(win => {
if (win.isDestroyed() || win.id == event.sender.id) return;
win.webContents.send('accept-detached-tab', JSON.parse(value));
})
})
ipcMain.on('new-window', (event, data, position) => {
if (typeof data == 'string') load_project_data = JSON.parse(data);
if (position) {
position = JSON.parse(position)
let place_in_window = all_wins.find(win => {
if (win.isDestroyed() || win.webContents == event.sender || win.isMinimized()) return false;
let pos = win.getPosition();
let size = win.getSize();
return (position.offset[0] >= pos[0] && position.offset[0] <= pos[0] + size[0]
&& position.offset[1] >= pos[1] && position.offset[1] <= pos[1] + size[1]);
})
if (place_in_window) {
place_in_window.send('load-tab', load_project_data);
place_in_window.focus();
load_project_data = null;
} else {
createWindow(true, {
maximize: false,
position: position.offset
});
}
} else {
createWindow(true);
}
})
ipcMain.on('close-detached-project', async (event, window_id, uuid) => {
let window = all_wins.find(win => win.id == window_id);
if (window) window.send('close-detached-project', uuid);
})
ipcMain.on('request-color-picker', async (event, arg) => {
const color = await getColorHexRGB().catch((error) => {
console.warn('[Error] Failed to pick color', error)
return ''
})
if (color) {
all_wins.forEach(win => {
if (win.isDestroyed() || (!arg.sync && win.webContents.getProcessId() != event.sender.getProcessId())) return;
win.webContents.send('set-main-color', color)
})
}
})
ipcMain.on('show-item-in-folder', async (event, path) => {
shell.showItemInFolder(path);
})
ipcMain.on('open-in-default-app', async (event, path) => {
shell.openPath(path);
})
app.on('ready', () => {
createWindow()
let app_was_loaded = false;
ipcMain.on('app-loaded', () => {
if (load_project_data) {
all_wins[all_wins.length-1].send('load-tab', load_project_data);
load_project_data = null;
}
if (app_was_loaded) {
console.log('[Blockbench] App reloaded or new window opened')
return;
}
app_was_loaded = true;
if (process.execPath && process.execPath.match(/node_modules[\\\/]electron/)) {
console.log('[Blockbench] App launched in development mode')
} else {
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.autoDownload = false;
if (LaunchSettings.get('update_to_prereleases') === true) {
autoUpdater.allowPrerelease = true;
//autoUpdater.channel = 'beta';
}
autoUpdater.on('update-available', (a) => {
console.log('update-available', a)
ipcMain.on('allow-auto-update', () => {
autoUpdater.downloadUpdate()
})
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-available', a);
})
autoUpdater.on('update-downloaded', (a) => {
console.log('update-downloaded', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-downloaded', a)
})
autoUpdater.on('error', (a) => {
console.log('update-error', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-error', a)
})
autoUpdater.on('download-progress', (a) => {
console.log('update-progress', a)
if (!orig_win.isDestroyed()) orig_win.webContents.send('update-progress', a)
})
autoUpdater.checkForUpdates().catch(err => {})
}
})
})
app.on('window-all-closed', () => {
app.quit()
})

118
electron/preload/index.ts Normal file
View File

@ -0,0 +1,118 @@
import { ipcRenderer, contextBridge } from 'electron'
// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld('ipcRenderer', {
on(...args: Parameters<typeof ipcRenderer.on>) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
},
off(...args: Parameters<typeof ipcRenderer.off>) {
const [channel, ...omit] = args
return ipcRenderer.off(channel, ...omit)
},
send(...args: Parameters<typeof ipcRenderer.send>) {
const [channel, ...omit] = args
return ipcRenderer.send(channel, ...omit)
},
invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
const [channel, ...omit] = args
return ipcRenderer.invoke(channel, ...omit)
},
// You can expose other APTs you need here.
// ...
})
// --------- Preload scripts loading ---------
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true)
} else {
document.addEventListener('readystatechange', () => {
if (condition.includes(document.readyState)) {
resolve(true)
}
})
}
})
}
const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find(e => e === child)) {
return parent.appendChild(child)
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find(e => e === child)) {
return parent.removeChild(child)
}
},
}
/**
* https://tobiasahlin.com/spinkit
* https://connoratherton.com/loaders
* https://projects.lukehaas.me/css-loaders
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = `loaders-css__square-spin`
const styleContent = `
@keyframes square-spin {
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {
animation-fill-mode: both;
width: 50px;
height: 50px;
background: #fff;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #282c34;
z-index: 9;
}
`
const oStyle = document.createElement('style')
const oDiv = document.createElement('div')
oStyle.id = 'app-loading-style'
oStyle.innerHTML = styleContent
oDiv.className = 'app-loading-wrap'
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
return {
appendLoading() {
safeDOM.append(document.head, oStyle)
safeDOM.append(document.body, oDiv)
},
removeLoading() {
safeDOM.remove(document.head, oStyle)
safeDOM.remove(document.body, oDiv)
},
}
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading()
}
setTimeout(removeLoading, 4999)

View File

@ -1,3 +1,5 @@
import Wintersky from 'wintersky';
export class AnimationControllerState {
constructor(controller, data = 0) {
this.controller = controller;

View File

@ -1,3 +1,6 @@
import MolangParser from "molangjs";
import Wintersky from 'wintersky';
export const Animator = {
get possible_channels() {
let obj = {};
@ -7,7 +10,7 @@ export const Animator = {
open: false,
get animations() {return Animation.all},
get selected() {return Animation.selected},
MolangParser: new Molang(),
MolangParser: new MolangParser(),
motion_trail: new THREE.Object3D(),
onion_skin_object: new THREE.Object3D(),
motion_trail_lock: false,
@ -1496,5 +1499,6 @@ Interface.definePanels(function() {
Object.assign(window, {
Animator,
Wintersky,
WinterskyScene
});

View File

@ -1,3 +1,5 @@
import Wintersky from 'wintersky';
export class GeneralAnimator {
constructor(uuid, animation) {
this.animation = animation;

View File

@ -1,4 +1,5 @@
import { Blockbench } from "./api";
import { ipcRenderer } from "./desktop";
import { loadInstalledPlugins } from "./plugin_loader";
import { animate } from "./preview/preview";
import { initializeWebApp, loadInfoFromURL } from "./web";

View File

@ -1,12 +1,10 @@
export const electron = require('@electron/remote');
console.log('REMOTE', electron)
export const {clipboard, shell, nativeImage, ipcRenderer, dialog, webUtils} = require('electron');
export const app = electron.app;
export const fs = require('fs');
export const NodeBuffer = require('buffer');
export const zlib = require('zlib');
export const exec = require('child_process').exec;
export const originalFs = require('original-fs');
export const https = require('https');
export const PathModule = require('path');
@ -588,6 +586,7 @@ BARS.defineActions(() => {
//Close
window.onbeforeunload = function (event) {
console.log('BEFORE UNLOAD')
try {
updateRecentProjectData()
} catch(err) {}
@ -756,13 +755,12 @@ ipcRenderer.on('update-available', (event, arg) => {
Object.assign(window, {
electron,
clipboard,
clipboard, shell, nativeImage, ipcRenderer, dialog, webUtils,
app,
fs,
NodeBuffer,
zlib,
exec,
originalFs,
https,
PathModule,
currentwindow,

View File

@ -1,4 +1,4 @@
var ground_animation = false;
var ground_timer = 0
var display_slot = 'thirdperson_righthand';
var display_presets;
@ -540,7 +540,7 @@ export class refModel {
DisplayMode.vue.reference_model = this.id;
if (display_slot == 'ground') {
ground_animation = this.id != 'fox';
Canvas.ground_animation = this.id != 'fox';
}
ReferenceImage.updateAll()
@ -1529,7 +1529,7 @@ export function loadDisp(key) { //Loads The Menu and slider values, common for a
display_preview.loadAnglePreset(display_angle_preset)
}
display_preview.controls.enabled = true;
ground_animation = false;
Canvas.ground_animation = false;
$('#display_crosshair').detach()
if (display_preview.orbit_gizmo) display_preview.orbit_gizmo.unhide();
display_preview.camPers.setFocalLength(45)
@ -1626,7 +1626,7 @@ DisplayMode.loadGround = function() { //Loader
target: [0, 3, 0]
})
setDisplayArea(8, 4, 8, 0, 0, 0, 1, 1, 1)
ground_animation = true;
Canvas.ground_animation = true;
ground_timer = 0
displayReferenceObjects.bar(['block', 'fox'])
}

View File

@ -1,3 +1,5 @@
import MolangParser from "molangjs";
export const Toolbars = {};
export const BarItems = {};
//Bars
@ -1066,7 +1068,7 @@ export class NumSlider extends Widget {
this.dispatchEvent('update');
}
}
NumSlider.MolangParser = new Molang()
NumSlider.MolangParser = new MolangParser()
export class BarSlider extends Widget {
constructor(id, data) {

View File

@ -90,8 +90,8 @@ export class Panel extends EventSystem {
//updateInterfacePanels()
})
}
this.vue = this.inside_vue = new Vue(data.component).$mount(component_mount);
scope.vue.$el.classList.add('panel_vue_wrapper');
this.vue = this.inside_vue = new Vue(data.component).$mount(component_mount);
this.vue.$el.classList.add('panel_vue_wrapper');
}
if (!Blockbench.isMobile) {

View File

@ -1,5 +1,6 @@
//import { createApp } from 'vue'
//import App from './App.vue'
import "../lib/libs"
import "../lib/vue.min.js"
import "../lib/vue_sortable.js"
@ -23,8 +24,6 @@ import "../lib/three_custom.js"
import "../lib/CanvasFrame.js"
import "../lib/canvas2apng.js"
//import "../lib/fik.min.js"
import "../lib/molang.umd.js"
import "../lib/wintersky.umd.js"
import "../lib/easing.js"
import "./preview/OrbitControls.js"
@ -59,8 +58,8 @@ import "./interface/action_control.js"
import "./copy_paste.js"
import "./undo.js"
//import './desktop.js'
import './web.js'
import './desktop.js'
//import './web.js'
import "./edit_sessions.js"
import "./validator.js"
@ -88,7 +87,6 @@ import "./texturing/texture_flipbook.js"
import "./texturing/uv.js"
import "./texturing/painter.js"
import "./texturing/texture_generator.js"
import "./texturing/color.js"
import "./texturing/edit_image.js"
import "./display_mode.js"
import "./animations/animation_mode.js"
@ -106,6 +104,7 @@ import "./io/format.js"
import "./io/project.js"
import "./io/io.js"
import "./io/share.js"
import "./texturing/color.js"
import "./io/formats/generic.js"
import "./io/formats/bbmodel.js"
import "./io/formats/java_block.js"

View File

@ -555,3 +555,5 @@ BARS.defineActions(() => {
}
})
})
Object.assign(window, {MirrorModeling});

View File

@ -192,7 +192,7 @@ BARS.defineActions(function() {
Outliner.elements.forEach(cube => {
if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube);
})
$('#main_colorpicker').spectrum('set', ColorPanel.vue._data.main_color);
$('#main_colorpicker').spectrum('set', ColorPanel.panel.vue._data.main_color);
if (StateMemory.color_picker_rgb) {
BarItems.slider_color_red.update();
BarItems.slider_color_green.update();

View File

@ -1,3 +1,5 @@
import { Property } from "../util/property";
export class MeshFace extends Face {
constructor(mesh, data) {
super(data);

View File

@ -282,7 +282,7 @@ export class Plugin {
// Download files
async function copyFileToDrive(origin_filename, target_filename, callback) {
var file = originalFs.createWriteStream(PathModule.join(Plugins.path, target_filename));
var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename));
https.get(Plugins.api_path+'/'+origin_filename, function(response) {
response.pipe(file);
if (callback) response.on('end', callback);
@ -406,7 +406,7 @@ export class Plugin {
// Save
if (isApp) {
await new Promise((resolve, reject) => {
let file = originalFs.createWriteStream(Plugins.path+this.id+'.js')
let file = fs.createWriteStream(Plugins.path+this.id+'.js')
https.get(url, (response) => {
response.pipe(file);
response.on('end', resolve)

View File

@ -116,6 +116,7 @@ export const Canvas = {
// Pivot marker
pivot_marker: rot_origin,
gizmos: [rot_origin],
ground_animation: false,
outlineMaterial: new THREE.LineBasicMaterial({
linewidth: 2,
depthTest: settings.seethrough_outline.value == false,
@ -856,9 +857,9 @@ export const Canvas = {
obj.was_visible = obj.visible
obj.visible = false
})
var ground_anim_before = ground_animation
if (Modes.display && ground_animation) {
ground_animation = false
var ground_anim_before = Canvas.ground_animation
if (Modes.display && Canvas.ground_animation) {
Canvas.ground_animation = false
}
updateCubeHighlights(null, true);
@ -873,7 +874,7 @@ export const Canvas = {
delete obj.was_visible
})
if (Modes.display && ground_anim_before) {
ground_animation = ground_anim_before
Canvas.ground_animation = ground_anim_before
}
updateCubeHighlights();
},

View File

@ -2112,7 +2112,7 @@ export function animate() {
}
})
framespersecond++;
if (Modes.display === true && ground_animation === true && !Transformer.hoverAxis) {
if (Modes.display === true && Canvas.ground_animation === true && !Transformer.hoverAxis) {
DisplayMode.groundAnimation()
}
Blockbench.dispatchEvent('render_frame');

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
export class Property {
constructor(target_class, type = 'boolean', name, options = 0) {
constructor(target_class, type = 'boolean', name, options = {}) {
if (!target_class.properties) {
target_class.properties = {};
}

View File

@ -1,9 +0,0 @@
{
"include": [
"js/**/*",
"types/**/*"
],
"compilerOptions": {
"module": "ES6"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

80
package-lock.json generated
View File

@ -14,13 +14,18 @@
"electron-updater": "^6.3.4",
"gifenc": "^1.0.3",
"jquery": "^3.7.1",
"tinycolor2": "^1.6.0"
"molangjs": "^1.6.5",
"tinycolor2": "^1.6.0",
"wintersky": "^1.3.2"
},
"devDependencies": {
"@electron/notarize": "^2.5.0",
"electron": "^33.3.1",
"electron-builder": "^24.13.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
"vite-plugin-pwa": "^0.21.1",
"workbox-build": "^6.5.3"
}
@ -4169,20 +4174,6 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/config-file-ts/node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -6304,6 +6295,12 @@
"node": ">=10"
}
},
"node_modules/molangjs": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/molangjs/-/molangjs-1.6.5.tgz",
"integrity": "sha512-XekNpUZ3RP5fsOHqic+tj9wOOTrN2vU6Ynrk8oM7lt9jlYoBzz1mzM1FxeSMqqpuDQrjEINS46Zxz6K1oOEnbA==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -7502,6 +7499,12 @@
"node": ">=10"
}
},
"node_modules/three": {
"version": "0.134.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.134.0.tgz",
"integrity": "sha512-LbBerg7GaSPjYtTOnu41AMp7tV6efUNR3p4Wk5NzkSsNTBuA5mDGOfwwZL1jhhVMLx9V20HolIUo0+U3AXehbg==",
"license": "MIT"
},
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
@ -7609,6 +7612,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -7843,6 +7860,28 @@
}
}
},
"node_modules/vite-plugin-electron": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/vite-plugin-electron/-/vite-plugin-electron-0.29.0.tgz",
"integrity": "sha512-HP0DI9Shg41hzt55IKYVnbrChWXHX95QtsEQfM+szQBpWjVhVGMlqRjVco6ebfQjWNr+Ga+PeoBjMIl8zMaufw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"vite-plugin-electron-renderer": "*"
},
"peerDependenciesMeta": {
"vite-plugin-electron-renderer": {
"optional": true
}
}
},
"node_modules/vite-plugin-electron-renderer": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite-plugin-pwa": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.1.tgz",
@ -8328,6 +8367,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wintersky": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/wintersky/-/wintersky-1.3.2.tgz",
"integrity": "sha512-QnhQF9/5dApuy55xqkhsGTsYpXjsPRTIRcoY3eFGkmtOXmj+AGXusvLg66EIApzYBAYQzyQetePkN4Tr0OxpYQ==",
"license": "MIT",
"dependencies": {
"molangjs": "^1.6.3",
"three": "^0.134.0",
"tinycolor2": "^1.4.2"
}
},
"node_modules/workbox-background-sync": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.3.tgz",

View File

@ -12,8 +12,13 @@
"type": "git",
"url": "https://github.com/JannisX11/blockbench"
},
"main": "main.js",
"main": "electron/main.js",
"type": "module",
"debug": {
"env": {
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
}
},
"build": {
"afterSign": "scripts/notarize.js",
"appId": "blockbench",
@ -115,22 +120,25 @@
]
},
"scripts": {
"dev": "vite",
"dev-electron": "electron . && vite",
"build": "vite build",
"preview": "vite preview",
"dist": "electron-builder",
"build-beta": "electron-builder --windows portable",
"publish-windows": "npm run build && electron-builder -w --publish=onTagOrDraft && node ./scripts/rename_portable.js && electron-builder --windows portable --publish=onTagOrDraft",
"prepublish": "npm run build",
"prepublish-beta": "node ./scripts/enable_beta.js && npm run build",
"dev": "vite --config vite.electron.config.js",
"dev-web": "vite --config vite.web.config.js",
"build-web": "vite build --config vite.web.config.js",
"build-electron": "vite build --config vite.web.electron.js",
"preview": "vite preview --config vite.web.config.js",
"build-beta": "npm run build-electron && electron-builder --windows portable",
"publish-windows": "npm run build-electron && electron-builder -w --publish=onTagOrDraft && node ./scripts/rename_portable.js && electron-builder --windows portable --publish=onTagOrDraft",
"prepublish": "npm run build-web",
"prepublish-beta": "node ./scripts/enable_beta.js && npm run build-web",
"webapp": "git checkout gh-pages && git pull && git merge master && git push && git checkout master"
},
"devDependencies": {
"@electron/notarize": "^2.5.0",
"electron": "^33.3.1",
"electron-builder": "^24.13.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
"vite-plugin-pwa": "^0.21.1",
"workbox-build": "^6.5.3"
},
@ -140,6 +148,8 @@
"electron-updater": "^6.3.4",
"gifenc": "^1.0.3",
"jquery": "^3.7.1",
"tinycolor2": "^1.6.0"
"molangjs": "^1.6.5",
"tinycolor2": "^1.6.0",
"wintersky": "^1.3.2"
}
}

20
tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"include": [
"js/**/*",
"types/**/*"
],
"compilerOptions": {
"target": "ES6",
"useDefineForClassFields": true,
"module": "ES6",
"moduleResolution": "Node",
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ES6", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"allowJs": true
}
}

View File

@ -1,45 +0,0 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
// import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
build: {
outDir: './dist-vite'
},
plugins: [
VitePWA({
registerType: 'autoUpdate',
workbox: {
cacheId: 'blockbench',
globDirectory: './',
globPatterns: [
'./index.html',
'./favicon.png',
'./icon_maskable.png',
'./js/**/*',
'./bundle.js',
'./lib/**/*',
'./css/**/*',
'./assets/**/*',
'./font/*',
],
swDest: './service_worker.js',
maximumFileSizeToCacheInBytes: 4_096_000,
sourcemap: false
}
})
//vue(),
],
/*
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
*/
})

74
vite.electron.config.js Normal file
View File

@ -0,0 +1,74 @@
import fs from 'node:fs'
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron/simple'
import pkg from './package.json'
// import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
fs.rmSync('dist-electron', { recursive: true, force: true })
const isServe = command === 'serve'
const isBuild = command === 'build'
const sourcemap = isServe || !!process.env.VSCODE_DEBUG
return {
build: {
outDir: './dist-vite'
},
plugins: [
// vue(),
electron({
main: {
// Shortcut of `build.lib.entry`
entry: 'electron/main.js',
onstart({ startup }) {
if (process.env.VSCODE_DEBUG) {
console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App')
} else {
startup()
}
},
vite: {
build: {
sourcemap,
minify: isBuild,
outDir: 'dist-electron/main',
rollupOptions: {
// Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons,
// we can use `external` to exclude them to ensure they work correctly.
// Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built.
// Of course, this is not absolute, just this way is relatively simple. :)
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
},
},
},
},
preload: {
// Shortcut of `build.rollupOptions.input`.
// Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
input: 'electron/preload/index.ts',
vite: {
build: {
sourcemap: sourcemap ? 'inline' : undefined, // #332
minify: isBuild,
outDir: 'dist-electron/preload',
rollupOptions: {
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
},
},
},
},
// Ployfill the Electron and Node.js API for Renderer process.
// If you want use Node.js in Renderer process, the `nodeIntegration` needs to be enabled in the Main process.
// See 👉 https://github.com/electron-vite/vite-plugin-electron-renderer
renderer: {},
}),
],
server: {
cors: false,
},
clearScreen: false,
}
})

41
vite.web.config.js Normal file
View File

@ -0,0 +1,41 @@
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
import pkg from './package.json'
// import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
return {
build: {
outDir: './dist-vite'
},
plugins: [
// vue(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
cacheId: 'blockbench',
globDirectory: './',
globPatterns: [
'./index.html',
'./favicon.png',
'./icon_maskable.png',
'./js/**/*',
'./bundle.js',
'./lib/**/*',
'./css/**/*',
'./assets/**/*',
'./font/*',
],
swDest: './service_worker.js',
maximumFileSizeToCacheInBytes: 4_096_000,
sourcemap: false
}
}),
],
clearScreen: false,
}
})