From 00e8b7bb8566037b2c41ce9e3dc60d3b01651ee5 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Mon, 8 Sep 2025 12:56:21 -0400 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20Add=20Prettier=20code=20formatt?= =?UTF-8?q?ing=20for=20.ts=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 3 +++ .prettierrc.json | 28 ++++++++++++++++++++++++++ .vscode/settings.json | 10 +++++++--- package-lock.json | 46 +++++++++++++++++++++++++++++++++++++------ package.json | 4 +++- 5 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..341b9d4b5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +build +node_modules +**/*.js \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..f073eda2c --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "tabWidth": 4, + "useTabs": true, + "semi": false, + "singleQuote": true, + "arrowParens": "avoid", + "printWidth": 100, + "trailingComma": "es5", + "bracketSameLine": false, + "endOfLine": "auto", + "overrides": [ + { + "files": ["**/*.yml", "**/*.yaml"], + "options": { + "parser": "yaml", + "tabWidth": 2, + "useTabs": false + } + }, + { + "files": "**/*.ts", + "options": { + "parser": "typescript" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index c082db11c..e39aa4103 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { - "editor.indentSize": "tabSize", - "editor.tabSize": 4, - "editor.insertSpaces": false + "editor.indentSize": "tabSize", + "editor.tabSize": 4, + "editor.insertSpaces": false, + "editor.formatOnSave": true, + // Disable foramtting and error checking of node_modules to reduce clutter and improve performance. + "npm.exclude": ["**/node_modules"], + "errorLens.excludePatterns": ["**/node_modules/**/*"] } diff --git a/package-lock.json b/package-lock.json index 90bc23359..3c2a4a512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "esbuild": "^0.25.8", "esbuild-plugin-glsl": "^1.2.2", "esbuild-vue": "^0.0.1", + "prettier": "^3.6.2", "typedoc": "^0.28.2", "typescript": "^5.8.3", "workbox-build": "^6.5.3" @@ -3395,6 +3396,22 @@ "prettier": "^1.18.2 || ^2.0.0" } }, + "node_modules/@vue/compiler-sfc/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@vue/component-compiler/-/component-compiler-4.2.4.tgz", @@ -3475,6 +3492,23 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/@vue/component-compiler-utils/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler-utils/node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -8134,16 +8168,16 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, "license": "MIT", - "optional": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/package.json b/package.json index 76da71010..3504ac781 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,8 @@ "generate-types": "tsc --project tsconfig.json && node ./scripts/convert_types.js", "publish-types": "npm publish ./types", "generate-docs": "node ./scripts/generate_docs.js", - "generate-wiki": "node ./scripts/generate_docs.js --out \"../../blockbench.net/content/api/\"" + "generate-wiki": "node ./scripts/generate_docs.js --out \"../../blockbench.net/content/api/\"", + "format": "prettier --write \"js/**/*.{ts,json,vue}\"" }, "overrides": { "three": "$three" @@ -153,6 +154,7 @@ "esbuild": "^0.25.8", "esbuild-plugin-glsl": "^1.2.2", "esbuild-vue": "^0.0.1", + "prettier": "^3.6.2", "typedoc": "^0.28.2", "typescript": "^5.8.3", "workbox-build": "^6.5.3" From f6dca80d1bcdb1020b9ddd1d9fae4ada2a869435 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Mon, 8 Sep 2025 12:57:53 -0400 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=8E=A8=20Format=20all=20ts=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/animations/molang_editor.ts | 66 +- js/api.ts | 343 +-- js/display_mode/DisplayModePanel.vue | 400 ++- js/display_mode/display_references.ts | 1425 +++++----- js/file_system.ts | 506 ++-- js/global_types.ts | 11 +- js/interface/dialog.ts | 987 ++++--- js/interface/form.ts | 976 ++++--- js/interface/panels.ts | 1518 ++++++----- js/interface/settings.ts | 501 ++-- js/interface/settings_window.ts | 264 +- js/interface/themes.ts | 715 ++--- js/io/format.ts | 316 +-- js/io/formats/fbx.ts | 3458 ++++++++++++++++-------- js/io/formats/generic.ts | 22 +- js/io/formats/skin.ts | 1110 ++++---- js/io/share.ts | 347 ++- js/languages.ts | 93 +- js/lib/CanvasFrame.ts | 117 +- js/lib/libs.ts | 62 +- js/main.ts | 234 +- js/modeling/edit.ts | 14 +- js/modeling/mesh/attach_armature.ts | 322 ++- js/modeling/mesh/proportional_edit.ts | 173 +- js/modeling/mesh/set_vertex_weights.ts | 56 +- js/modeling/mesh/util.ts | 2 +- js/modeling/mirror_modeling.ts | 818 +++--- js/modeling/weight_paint.ts | 254 +- js/modes.ts | 179 +- js/native_apis.ts | 180 +- js/native_apis_web.ts | 6 +- js/outliner/armature.ts | 204 +- js/outliner/armature_bone.ts | 548 ++-- js/outliner/collections.ts | 635 ++--- js/outliner/element_panel.ts | 124 +- js/plugin_loader.ts | 1343 ++++----- js/shaders/shader.ts | 15 +- js/texturing/ColorPickerNormal.vue | 184 +- js/util/event_system.ts | 56 +- js/util/global.d.ts | 4 +- js/util/json.ts | 133 +- js/util/molang.ts | 60 +- js/util/property.ts | 209 +- js/util/scoped_fs.ts | 164 +- js/util/state_memory.ts | 53 +- js/util/yaml.ts | 62 +- 46 files changed, 11127 insertions(+), 8142 deletions(-) diff --git a/js/animations/molang_editor.ts b/js/animations/molang_editor.ts index b3db12fd1..e92d45d70 100644 --- a/js/animations/molang_editor.ts +++ b/js/animations/molang_editor.ts @@ -1,10 +1,10 @@ -import { MolangAutocomplete } from "./molang"; +import { MolangAutocomplete } from './molang' interface MolangEditorOptions { autocomplete_context: MolangAutocomplete.Context text: string } -export function openMolangEditor(options: MolangEditorOptions, callback: ((result: string) => void)) { +export function openMolangEditor(options: MolangEditorOptions, callback: (result: string) => void) { interface VueData { text: string } @@ -13,47 +13,51 @@ export function openMolangEditor(options: MolangEditorOptions, callback: ((resul resizable: true, width: 800, component: { - components: {VuePrismEditor}, + components: { VuePrismEditor }, data: { - text: options.text + text: options.text, }, methods: { prettyPrint(this: VueData) { - this.text = this.text.replace(/;\s*(?!\n)/g, ';\n'); + this.text = this.text.replace(/;\s*(?!\n)/g, ';\n') }, minify(this: VueData) { - this.text = this.text.replace(/\n/g, '').replace(/\s{2,}/g, ' '); + this.text = this.text.replace(/\n/g, '').replace(/\s{2,}/g, ' ') }, findReplace(this: VueData) { this - let scope = this; + let scope = this new Dialog({ id: 'find_replace', title: 'action.find_replace', form: { - find: {label: 'dialog.find_replace.find', type: 'text'}, - replace: {label: 'dialog.find_replace.replace', type: 'text'}, - regex: {label: 'dialog.find_replace.regex', type: 'checkbox', value: false}, + find: { label: 'dialog.find_replace.find', type: 'text' }, + replace: { label: 'dialog.find_replace.replace', type: 'text' }, + regex: { + label: 'dialog.find_replace.regex', + type: 'checkbox', + value: false, + }, }, onConfirm(form) { - if (!form.find) return; + if (!form.find) return function replace(text: string) { if (form.regex) { - let regex = new RegExp(form.find, 'g'); - return text.replace(regex, form.replace); + let regex = new RegExp(form.find, 'g') + return text.replace(regex, form.replace) } else { - return text.split(form.find).join(form.replace); + return text.split(form.find).join(form.replace) } } - scope.text = replace(scope.text); - } - }).show(); + scope.text = replace(scope.text) + }, + }).show() }, autocomplete(text: string, position: number) { - if (Settings.get('autocomplete_code') == false) return []; - let test = options.autocomplete_context.autocomplete(text, position); - return test; - } + if (Settings.get('autocomplete_code') == false) return [] + let test = options.autocomplete_context.autocomplete(text, position) + return test + }, }, template: `
@@ -72,19 +76,23 @@ export function openMolangEditor(options: MolangEditorOptions, callback: ((resul :line-numbers="true" />
- ` + `, }, onOpen() { - let element = document.querySelector('#expression_editor_prism.molang_input') as HTMLElement; - element.style.height = (dialog.object.clientHeight - 50) + 'px'; + let element = document.querySelector( + '#expression_editor_prism.molang_input' + ) as HTMLElement + element.style.height = dialog.object.clientHeight - 50 + 'px' }, onResize() { - let element = document.querySelector('#expression_editor_prism.molang_input') as HTMLElement; - element.style.height = (dialog.object.clientHeight - 50) + 'px'; + let element = document.querySelector( + '#expression_editor_prism.molang_input' + ) as HTMLElement + element.style.height = dialog.object.clientHeight - 50 + 'px' }, onConfirm() { - callback(dialog.content_vue.$data.text); - } + callback(dialog.content_vue.$data.text) + }, }) - dialog.show(); + dialog.show() } diff --git a/js/api.ts b/js/api.ts index 297f404e0..3ceb5c60d 100644 --- a/js/api.ts +++ b/js/api.ts @@ -1,16 +1,15 @@ -import { FormElementOptions } from "./interface/form"; -import { ModelFormat } from "./io/format"; -import { Prop } from "./misc"; -import { EventSystem } from "./util/event_system"; -import { compareVersions } from "./util/util"; -import { Filesystem } from "./file_system"; -import { MessageBoxOptions } from "./interface/dialog"; -import { currentwindow, shell, SystemInfo } from "./native_apis"; +import { FormElementOptions } from './interface/form' +import { ModelFormat } from './io/format' +import { Prop } from './misc' +import { EventSystem } from './util/event_system' +import { compareVersions } from './util/util' +import { Filesystem } from './file_system' +import { MessageBoxOptions } from './interface/dialog' +import { currentwindow, shell, SystemInfo } from './native_apis' -declare const appVersion: string; +declare const appVersion: string declare let Format: ModelFormat - interface ToastNotificationOptions { /** * Text message @@ -29,12 +28,13 @@ interface ToastNotificationOptions { */ color?: string /** - * Method to run on click. + * Method to run on click. * @returns Return `true` to close toast */ click?: (event: Event) => boolean } -export const LastVersion = localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion; +export const LastVersion = + localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion export const Blockbench = { ...window.Blockbench, @@ -43,7 +43,7 @@ export const Blockbench = { isLandscape: window.innerWidth > window.innerHeight, isTouch: 'ontouchend' in document, get isPWA() { - return 'standalone' in navigator || window.matchMedia('(display-mode: standalone)').matches; + return 'standalone' in navigator || window.matchMedia('(display-mode: standalone)').matches }, version: appVersion, operating_system: '', @@ -58,7 +58,7 @@ export const Blockbench = { */ edit(aspects: UndoAspects, cb: () => void) { Undo.initEdit(aspects) - cb(); + cb() Undo.finishEdit('Edit') }, reload() { @@ -72,153 +72,164 @@ export const Blockbench = { } }, isNewerThan(version: string): boolean { - return compareVersions(Blockbench.version, version); + return compareVersions(Blockbench.version, version) }, isOlderThan(version: string): boolean { - return compareVersions(version, Blockbench.version); + return compareVersions(version, Blockbench.version) }, registerEdit() { - console.warn('Blockbench.registerEdit is outdated. Please use Undo.initEdit and Undo.finishEdit') + console.warn( + 'Blockbench.registerEdit is outdated. Please use Undo.initEdit and Undo.finishEdit' + ) }, //Interface - getIconNode(icon: IconString | boolean | HTMLElement | (() => (IconString | boolean | HTMLElement)), color?: string) { - let node; + getIconNode( + icon: IconString | boolean | HTMLElement | (() => IconString | boolean | HTMLElement), + color?: string + ) { + let node if (typeof icon === 'function') { icon = icon() } if (icon === undefined) { //Missing - node = document.createElement('i'); - node.classList.add('material-icons', 'notranslate', 'icon'); - node.innerText = 'help_outline'; + node = document.createElement('i') + node.classList.add('material-icons', 'notranslate', 'icon') + node.innerText = 'help_outline' } else if (icon instanceof HTMLElement) { //Node node = icon } else if (icon === true || icon === false) { //Boolean - node = document.createElement('i'); - node.classList.add('material-icons', 'notranslate', 'icon'); - node.innerText = icon ? 'check_box' : 'check_box_outline_blank'; - + node = document.createElement('i') + node.classList.add('material-icons', 'notranslate', 'icon') + node.innerText = icon ? 'check_box' : 'check_box_outline_blank' } else if (icon === null) { //Node - node = document.createElement('i'); - node.classList.add('fa_big', 'icon'); - + node = document.createElement('i') + node.classList.add('fa_big', 'icon') } else if (icon.match(/^(fa[.-])|(fa[rsb]\.)/)) { //Font Awesome - node = document.createElement('i'); - node.classList.add('fa_big', 'icon'); + node = document.createElement('i') + node.classList.add('fa_big', 'icon') if (icon.substr(3, 1) === '.') { - node.classList.add(icon.substr(0, 3), icon.substr(4)); + node.classList.add(icon.substr(0, 3), icon.substr(4)) } else { - node.classList.add('fa', icon); + node.classList.add('fa', icon) } } else if (icon.substr(0, 5) === 'icon-') { //Icomoon - node = document.createElement('i'); - node.classList.add(icon, 'icon'); + node = document.createElement('i') + node.classList.add(icon, 'icon') } else if (icon.substr(0, 14) === 'data:image/png') { //Data URL - node = document.createElement('img'); - node.classList.add('icon'); - node.src = icon; + node = document.createElement('img') + node.classList.add('icon') + node.src = icon } else { //Material Icon - node = document.createElement('i'); - node.classList.add('material-icons', 'notranslate', 'icon'); - node.innerText = icon; + node = document.createElement('i') + node.classList.add('material-icons', 'notranslate', 'icon') + node.innerText = icon } if (color) { if (color === 'x') { - node.classList.add('color_x'); + node.classList.add('color_x') } else if (color === 'y') { - node.classList.add('color_y'); + node.classList.add('color_y') } else if (color === 'z') { - node.classList.add('color_z'); - } else if (color === 'u') { - node.classList.add('color_u'); - } else if (color === 'v') { - node.classList.add('color_v'); - } else if (color === 'w') { - node.classList.add('color_w'); + node.classList.add('color_z') + } else if (color === 'u') { + node.classList.add('color_u') + } else if (color === 'v') { + node.classList.add('color_v') + } else if (color === 'w') { + node.classList.add('color_w') } else if (typeof color === 'string') { - node.style.color = color; + node.style.color = color } } return node }, showQuickMessage(message, time = 1000) { - document.getElementById('quick_message_box')?.remove(); - let quick_message_box = Interface.createElement('div', {id: 'quick_message_box'}, tl(message)); - document.body.append(quick_message_box); + document.getElementById('quick_message_box')?.remove() + let quick_message_box = Interface.createElement( + 'div', + { id: 'quick_message_box' }, + tl(message) + ) + document.body.append(quick_message_box) - setTimeout(function() { + setTimeout(function () { quick_message_box.remove() - }, time); + }, time) }, showToastNotification(options: ToastNotificationOptions) { - let notification = document.createElement('li'); - notification.className = 'toast_notification'; + let notification = document.createElement('li') + notification.className = 'toast_notification' if (options.icon) { - let icon = Blockbench.getIconNode(options.icon); - notification.append(icon); + let icon = Blockbench.getIconNode(options.icon) + notification.append(icon) } - let text = document.createElement('span'); - text.innerText = tl(options.text); - notification.append(text); + let text = document.createElement('span') + text.innerText = tl(options.text) + notification.append(text) - let close_button = document.createElement('div'); - close_button.innerHTML = 'clear'; - close_button.className = 'toast_close_button'; - close_button.addEventListener('click', (event) => { - notification.remove(); + let close_button = document.createElement('div') + close_button.innerHTML = 'clear' + close_button.className = 'toast_close_button' + close_button.addEventListener('click', event => { + notification.remove() }) - notification.append(close_button); + notification.append(close_button) if (options.color) { - notification.style.backgroundColor = options.color; + notification.style.backgroundColor = options.color } if (typeof options.click == 'function') { - notification.addEventListener('click', (event) => { - if (event.target == close_button || (event.target as HTMLElement).parentElement == close_button) return; - let result = options.click(event); + notification.addEventListener('click', event => { + if ( + event.target == close_button || + (event.target as HTMLElement).parentElement == close_button + ) + return + let result = options.click(event) if (result == true) { - notification.remove(); + notification.remove() } }) - notification.style.cursor = 'pointer'; + notification.style.cursor = 'pointer' } if (options.expire) { setTimeout(() => { - notification.remove(); - }, options.expire); + notification.remove() + }, options.expire) } - document.getElementById('toast_notification_list').append(notification); + document.getElementById('toast_notification_list').append(notification) function deletableToast(node: HTMLElement) { - this.delete = function() { - node.remove(); + this.delete = function () { + node.remove() } } - return new deletableToast(notification); + return new deletableToast(notification) }, setCursorTooltip(text?: string): void {}, setProgress(progress: number, time: number = 0, bar?: string): void {}, showStatusMessage(message: string, time: number = 800) { Blockbench.setStatusBarText(tl(message)) - setTimeout(function() { + setTimeout(function () { Blockbench.setStatusBarText() - }, time); + }, time) }, setStatusBarText(text?: string) { if (text !== undefined) { Prop.file_name = text } else { - Prop.file_name = Prop.file_name_alt||'' + Prop.file_name = Prop.file_name_alt || '' } }, showMessage(message, location) { @@ -228,58 +239,72 @@ export const Blockbench = { Blockbench.showQuickMessage(message) } }, - showMessageBox(options: MessageBoxOptions, cb?: (button: number | string, result?: Record, event?: Event) => void) { - return new MessageBox(options, cb).show(); + showMessageBox( + options: MessageBoxOptions, + cb?: (button: number | string, result?: Record, event?: Event) => void + ) { + return new MessageBox(options, cb).show() }, /** - * - * @param {*} title - * @param {*} value - * @param {*} callback + * + * @param {*} title + * @param {*} value + * @param {*} callback * @param {object} options Options * @param {string} options.info Info text * @param {string} options.description Description for the text input * @returns {Promise} Input value */ - async textPrompt(title: string, value: string, callback: (text: string) => void, options: {placeholder?: string, description?: string, info?: string} = {}) { + async textPrompt( + title: string, + value: string, + callback: (text: string) => void, + options: { placeholder?: string; description?: string; info?: string } = {} + ) { if (typeof options == 'string') { - options = {placeholder: options}; - console.warn('textPrompt: 4th argument is expected to be an object'); + options = { placeholder: options } + console.warn('textPrompt: 4th argument is expected to be an object') } - let answer = await new Promise((resolve) => { + let answer = await new Promise(resolve => { let form: Record = { - text: {type: 'text', full_width: true, placeholder: options.placeholder, value, description: options.description}, - }; + text: { + type: 'text', + full_width: true, + placeholder: options.placeholder, + value, + description: options.description, + }, + } if (options.info) { form.description = { type: 'info', - text: tl(options.info) + text: tl(options.info), } } new Dialog({ id: 'text_input', title: title || 'dialog.input.title', form, - onConfirm({text}) { - if (callback) callback(text); - resolve(text); + onConfirm({ text }) { + if (callback) callback(text) + resolve(text) }, onOpen() { - this.object.querySelector('input')?.focus(); - } - }).show(); - }); - return answer; + this.object.querySelector('input')?.focus() + }, + }).show() + }) + return answer }, addMenuEntry(name: string, icon: IconString, click) { console.warn('Blockbench.addMenuEntry is deprecated. Please use Actions instead.') - let id = name.replace(/\s/g, '').toLowerCase(); - var action = new Action(id, {icon: icon, name: name, click: click}) + let id = name.replace(/\s/g, '').toLowerCase() + var action = new Action(id, { icon: icon, name: name, click: click }) MenuBar.addAction(action, 'tools') }, removeMenuEntry(name: string) { - let id = name.replace(/\s/g, '').toLowerCase(); - MenuBar.removeAction('tools.'+id); + let id = name.replace(/\s/g, '').toLowerCase() + MenuBar.removeAction('tools.' + id) }, openLink(link: string) { if (isApp) { @@ -291,13 +316,13 @@ export const Blockbench = { notification(title: string, text: string, icon?: string) { Notification.requestPermission().then(status => { if (status == 'granted') { - let n = new Notification(title, {body: text, icon: icon||'favicon.png'}) - n.onclick = function() { + let n = new Notification(title, { body: text, icon: icon || 'favicon.png' }) + n.onclick = function () { if (isApp) { // @ts-ignore - currentwindow.focus(); + currentwindow.focus() } else { - window.focus(); + window.focus() } } } @@ -305,68 +330,72 @@ export const Blockbench = { }, //CSS addCSS(css: string): Deletable { - let style_node = document.createElement('style'); - style_node.type ='text/css'; - style_node.appendChild(document.createTextNode(css)); - document.getElementsByTagName('head')[0].appendChild(style_node); + let style_node = document.createElement('style') + style_node.type = 'text/css' + style_node.appendChild(document.createTextNode(css)) + document.getElementsByTagName('head')[0].appendChild(style_node) function deletableStyle(node) { - this.delete = function() { - node.remove(); + this.delete = function () { + node.remove() } } - return new deletableStyle(style_node); + return new deletableStyle(style_node) }, //Flags addFlag(flag: string): void { - this.flags[flag] = true; + this.flags[flag] = true }, removeFlag(flag: string): void { - delete this.flags[flag]; + delete this.flags[flag] }, hasFlag(flag: string): boolean | undefined { - return this.flags[flag]; + return this.flags[flag] }, //Events dispatchEvent(event_name: EventName, data: any): any[] { - let list = this.events[event_name]; - let results: any[]; + let list = this.events[event_name] + let results: any[] if (list) { - results = []; + results = [] for (let i = 0; i < list.length; i++) { if (typeof list[i] === 'function') { - let result = list[i](data); - results.push(result); + let result = list[i](data) + results.push(result) } } } if (Validator.triggers.includes(event_name)) { - Validator.validate(event_name); + Validator.validate(event_name) } - return results; + return results }, on(event_name: EventName, cb) { - return EventSystem.prototype.on.call(this, event_name, cb); + return EventSystem.prototype.on.call(this, event_name, cb) }, once(event_name: EventName, cb) { - return EventSystem.prototype.once.call(this, event_name, cb); + return EventSystem.prototype.once.call(this, event_name, cb) }, addListener(event_name: EventName, cb) { - return EventSystem.prototype.addListener.call(this, event_name, cb); + return EventSystem.prototype.addListener.call(this, event_name, cb) }, removeListener(event_name: EventName, cb) { - return EventSystem.prototype.removeListener.call(this, event_name, cb); + return EventSystem.prototype.removeListener.call(this, event_name, cb) }, // Update onUpdateTo(version, callback) { - if (LastVersion && compareVersions(version, LastVersion) && !Blockbench.isOlderThan(version)) { - callback(LastVersion); + if ( + LastVersion && + compareVersions(version, LastVersion) && + !Blockbench.isOlderThan(version) + ) { + callback(LastVersion) } }, // Globals - Format: 0 as (ModelFormat | number), - Project: 0 as (ModelProject | number), + Format: 0 as ModelFormat | number, + Project: 0 as ModelProject | number, get Undo() { - return Project?.undo; + return Project?.undo }, // File System import: Filesystem.importFile, @@ -380,34 +409,40 @@ export const Blockbench = { findFileFromContent: Filesystem.findFileFromContent, addDragHandler: Filesystem.addDragHandler, removeDragHandler: Filesystem.removeDragHandler, -}; +} -(function() { +;(function () { if (!LastVersion || LastVersion.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) { - Blockbench.addFlag('after_update'); + Blockbench.addFlag('after_update') } else if (LastVersion != appVersion) { - Blockbench.addFlag('after_patch_update'); + Blockbench.addFlag('after_patch_update') } try { - let ui_mode = JSON.parse(localStorage.getItem('settings')).interface_mode.value; - if (ui_mode == 'desktop') Blockbench.isMobile = false; - if (ui_mode == 'mobile') Blockbench.isMobile = true; + let ui_mode = JSON.parse(localStorage.getItem('settings')).interface_mode.value + if (ui_mode == 'desktop') Blockbench.isMobile = false + if (ui_mode == 'mobile') Blockbench.isMobile = true } catch (err) {} -})(); +})() if (isApp) { - Blockbench.platform = SystemInfo.platform; + Blockbench.platform = SystemInfo.platform switch (Blockbench.platform) { - case 'win32': Blockbench.operating_system = 'Windows'; break; - case 'darwin': Blockbench.operating_system = 'macOS'; break; - default: Blockbench.operating_system = 'Linux'; break; + case 'win32': + Blockbench.operating_system = 'Windows' + break + case 'darwin': + Blockbench.operating_system = 'macOS' + break + default: + Blockbench.operating_system = 'Linux' + break } // @ts-ignore - if (Blockbench.platform.includes('win32') === true) window.osfs = '\\'; + if (Blockbench.platform.includes('win32') === true) window.osfs = '\\' } Object.assign(window, { LastVersion, Blockbench, - isApp -}); + isApp, +}) diff --git a/js/display_mode/DisplayModePanel.vue b/js/display_mode/DisplayModePanel.vue index c266f6b6b..139aa2b86 100644 --- a/js/display_mode/DisplayModePanel.vue +++ b/js/display_mode/DisplayModePanel.vue @@ -3,123 +3,299 @@

{{ tl('display.slot') }}

- - - - + + + + - - - - + + + + - - + + - - + + - - - - - + + - - + + + + +

{{ tl('display.reference') }}

-
-
+
-

{{ tl('display.rotation') }}

-
replay
+
+ replay +
-
- - +
+ +
- +

{{ tl('display.translation') }}

-
replay
+
+ replay
-
- +
+ - + value="0" + @input="change(axis, 'translation')" + @mousedown="start()" + @change="save" + /> +

{{ tl('display.scale') }}

-
flip
-
replay
+
+ flip +
+
+ replay +
-
-
+
+
{{ tl('display.mirror') }}
- {{ slot.mirror[axis] ? 'check_box' : 'check_box_outline_blank' }} + {{ + slot.mirror[axis] ? 'check_box' : 'check_box_outline_blank' + }}
- - + value="0" + @input="change(axis, 'scale')" + @mousedown="start(axis, 'scale')" + @change="save(axis, 'scale')" + /> +
-
- - +
+ +
- + - + \ No newline at end of file + diff --git a/js/display_mode/display_references.ts b/js/display_mode/display_references.ts index d1282be34..53702a9d2 100644 --- a/js/display_mode/display_references.ts +++ b/js/display_mode/display_references.ts @@ -1,4 +1,4 @@ -import { PreviewModel } from "../preview/preview_scenes"; +import { PreviewModel } from '../preview/preview_scenes' /** * @internal @@ -10,334 +10,334 @@ const DisplayReferences = { elements: [ { //Head - "name": "head", - "size": [8, 8, 8], - "pos": [0, 28, 0], - "origin": [0, 24, 0], - "north": {"uv": [8, 8, 16, 16]}, - "east": {"uv": [0, 8, 8, 16]}, - "south": {"uv": [24, 8, 32, 16]}, - "west": {"uv": [16, 8, 24, 16]}, - "up": {"uv": [16, 8, 8, 0]}, - "down": {"uv": [24, 0, 16, 8]} + name: 'head', + size: [8, 8, 8], + pos: [0, 28, 0], + origin: [0, 24, 0], + north: { uv: [8, 8, 16, 16] }, + east: { uv: [0, 8, 8, 16] }, + south: { uv: [24, 8, 32, 16] }, + west: { uv: [16, 8, 24, 16] }, + up: { uv: [16, 8, 8, 0] }, + down: { uv: [24, 0, 16, 8] }, }, { //Head Layer - "name": "head_layer", - "size": [9, 9, 9], - "pos": [0, 28, 0], - "origin": [0, 24, 0], - "north": {"uv": [40, 8, 48, 16]}, - "east": {"uv": [32, 8, 40, 16]}, - "south": {"uv": [56, 8, 64, 16]}, - "west": {"uv": [48, 8, 56, 16]}, - "up": {"uv": [48, 8, 40, 0]}, - "down": {"uv": [56, 0, 48, 8]} + name: 'head_layer', + size: [9, 9, 9], + pos: [0, 28, 0], + origin: [0, 24, 0], + north: { uv: [40, 8, 48, 16] }, + east: { uv: [32, 8, 40, 16] }, + south: { uv: [56, 8, 64, 16] }, + west: { uv: [48, 8, 56, 16] }, + up: { uv: [48, 8, 40, 0] }, + down: { uv: [56, 0, 48, 8] }, }, { //Body - "size": [8, 12, 4], - "pos": [0, 18, 0], - "north": {"uv": [20, 20, 28, 32]}, - "east": {"uv": [16, 20, 20, 32]}, - "south": {"uv": [32, 20, 40, 32]}, - "west": {"uv": [28, 20, 32, 32]}, - "up": {"uv": [20, 20, 28, 16]}, - "down": {"uv": [28, 16, 36, 20]} + size: [8, 12, 4], + pos: [0, 18, 0], + north: { uv: [20, 20, 28, 32] }, + east: { uv: [16, 20, 20, 32] }, + south: { uv: [32, 20, 40, 32] }, + west: { uv: [28, 20, 32, 32] }, + up: { uv: [20, 20, 28, 16] }, + down: { uv: [28, 16, 36, 20] }, }, { //Body Layer - "size": [8.5, 12.5, 4.5], - "pos": [0, 18, 0], - "north": {"uv": [20, 36, 28, 48]}, - "east": {"uv": [16, 36, 20, 48]}, - "south": {"uv": [32, 36, 40, 48]}, - "west": {"uv": [28, 36, 32, 48]}, - "up": {"uv": [20, 36, 28, 32]}, - "down": {"uv": [28, 32, 36, 36]} + size: [8.5, 12.5, 4.5], + pos: [0, 18, 0], + north: { uv: [20, 36, 28, 48] }, + east: { uv: [16, 36, 20, 48] }, + south: { uv: [32, 36, 40, 48] }, + west: { uv: [28, 36, 32, 48] }, + up: { uv: [20, 36, 28, 32] }, + down: { uv: [28, 32, 36, 36] }, }, { //R Leg - "size": [4, 12, 4], - "pos": [1.95, 6, 0], - "origin": [0, 12, 0], - "rotation": [-1, 0, 0], - "north": {"uv": [4, 20, 8, 32]}, - "east": {"uv": [0, 20, 4, 32]}, - "south": {"uv": [12, 20, 16, 32]}, - "west": {"uv": [8, 20, 12, 32]}, - "up": {"uv": [8, 20, 4, 16]}, - "down": {"uv": [12, 16, 8, 20]} + size: [4, 12, 4], + pos: [1.95, 6, 0], + origin: [0, 12, 0], + rotation: [-1, 0, 0], + north: { uv: [4, 20, 8, 32] }, + east: { uv: [0, 20, 4, 32] }, + south: { uv: [12, 20, 16, 32] }, + west: { uv: [8, 20, 12, 32] }, + up: { uv: [8, 20, 4, 16] }, + down: { uv: [12, 16, 8, 20] }, }, { //R Leg Layer - "size": [4.5, 12.5, 4.5], - "pos": [1.95, 6, 0], - "origin": [0, 12, 0], - "rotation": [-1, 0, 0], - "north": {"uv": [4, 36, 8, 48]}, - "east": {"uv": [0, 36, 4, 48]}, - "south": {"uv": [12, 36, 16, 48]}, - "west": {"uv": [8, 36, 12, 48]}, - "up": {"uv": [8, 36, 4, 32]}, - "down": {"uv": [12, 32, 8, 36]} + size: [4.5, 12.5, 4.5], + pos: [1.95, 6, 0], + origin: [0, 12, 0], + rotation: [-1, 0, 0], + north: { uv: [4, 36, 8, 48] }, + east: { uv: [0, 36, 4, 48] }, + south: { uv: [12, 36, 16, 48] }, + west: { uv: [8, 36, 12, 48] }, + up: { uv: [8, 36, 4, 32] }, + down: { uv: [12, 32, 8, 36] }, }, { //L Leg - "size": [4, 12, 4], - "pos": [-1.95, 6, 0], - "origin": [0, 12, 0], - "rotation": [1, 0, 0], - "north": {"uv": [20, 52, 24, 64]}, - "east": {"uv": [16, 52, 20, 64]}, - "south": {"uv": [28, 52, 32, 64]}, - "west": {"uv": [24, 52, 28, 64]}, - "up": {"uv": [24, 52, 20, 48]}, - "down": {"uv": [28, 48, 24, 52]} + size: [4, 12, 4], + pos: [-1.95, 6, 0], + origin: [0, 12, 0], + rotation: [1, 0, 0], + north: { uv: [20, 52, 24, 64] }, + east: { uv: [16, 52, 20, 64] }, + south: { uv: [28, 52, 32, 64] }, + west: { uv: [24, 52, 28, 64] }, + up: { uv: [24, 52, 20, 48] }, + down: { uv: [28, 48, 24, 52] }, }, { //L Leg Layer - "size": [4.5, 12.5, 4.5], - "pos": [-1.95, 6, 0], - "origin": [0, 12, 0], - "rotation": [1, 0, 0], - "north": {"uv": [4, 52, 8, 64]}, - "east": {"uv": [0, 52, 4, 64]}, - "south": {"uv": [12, 52, 16, 64]}, - "west": {"uv": [8, 52, 12, 64]}, - "up": {"uv": [8, 52, 4, 48]}, - "down": {"uv": [12, 48, 8, 52]} + size: [4.5, 12.5, 4.5], + pos: [-1.95, 6, 0], + origin: [0, 12, 0], + rotation: [1, 0, 0], + north: { uv: [4, 52, 8, 64] }, + east: { uv: [0, 52, 4, 64] }, + south: { uv: [12, 52, 16, 64] }, + west: { uv: [8, 52, 12, 64] }, + up: { uv: [8, 52, 4, 48] }, + down: { uv: [12, 48, 8, 52] }, }, //Steve { - "name": "right_arm", - "size": [4, 12, 4], - "pos": [6, 18, 0], - "origin": [4, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [44, 20, 48, 32]}, - "east": {"uv": [40, 20, 44, 32]}, - "south": {"uv": [52, 20, 56, 32]}, - "west": {"uv": [48, 20, 52, 32]}, - "up": {"uv": [48, 20, 44, 16]}, - "down": {"uv": [52, 16, 48, 20]}, - "model": "steve" - }, - { - "name": "right_arm_layer", - "size": [4.5, 12.5, 4.5], - "pos": [6, 18, 0], - "origin": [4, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [44, 36, 48, 48]}, - "east": {"uv": [40, 36, 44, 48]}, - "south": {"uv": [52, 36, 56, 48]}, - "west": {"uv": [48, 36, 52, 48]}, - "up": {"uv": [48, 36, 44, 32]}, - "down": {"uv": [52, 32, 48, 36]}, - "model": "steve" - }, - { - "name": "left_arm", - "size": [4, 12, 4], - "pos": [-6, 18, 0], - "origin": [-4, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [36, 52, 40, 64]}, - "east": {"uv": [32, 52, 36, 64]}, - "south": {"uv": [44, 52, 48, 64]}, - "west": {"uv": [40, 52, 44, 64]}, - "up": {"uv": [40, 52, 36, 48]}, - "down": {"uv": [44, 48, 40, 52]}, - "model": "steve" - }, - { - "name": "left_arm_layer", - "size": [4.5, 12.5, 4.5], - "pos": [-6, 18, 0], - "origin": [-4, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [52, 52, 56, 64]}, - "east": {"uv": [48, 52, 52, 64]}, - "south": {"uv": [60, 52, 64, 64]}, - "west": {"uv": [56, 52, 60, 64]}, - "up": {"uv": [56, 52, 52, 48]}, - "down": {"uv": [60, 48, 56, 52]}, - "model": "steve" + name: 'right_arm', + size: [4, 12, 4], + pos: [6, 18, 0], + origin: [4, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [44, 20, 48, 32] }, + east: { uv: [40, 20, 44, 32] }, + south: { uv: [52, 20, 56, 32] }, + west: { uv: [48, 20, 52, 32] }, + up: { uv: [48, 20, 44, 16] }, + down: { uv: [52, 16, 48, 20] }, + model: 'steve', + }, + { + name: 'right_arm_layer', + size: [4.5, 12.5, 4.5], + pos: [6, 18, 0], + origin: [4, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [44, 36, 48, 48] }, + east: { uv: [40, 36, 44, 48] }, + south: { uv: [52, 36, 56, 48] }, + west: { uv: [48, 36, 52, 48] }, + up: { uv: [48, 36, 44, 32] }, + down: { uv: [52, 32, 48, 36] }, + model: 'steve', + }, + { + name: 'left_arm', + size: [4, 12, 4], + pos: [-6, 18, 0], + origin: [-4, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [36, 52, 40, 64] }, + east: { uv: [32, 52, 36, 64] }, + south: { uv: [44, 52, 48, 64] }, + west: { uv: [40, 52, 44, 64] }, + up: { uv: [40, 52, 36, 48] }, + down: { uv: [44, 48, 40, 52] }, + model: 'steve', + }, + { + name: 'left_arm_layer', + size: [4.5, 12.5, 4.5], + pos: [-6, 18, 0], + origin: [-4, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [52, 52, 56, 64] }, + east: { uv: [48, 52, 52, 64] }, + south: { uv: [60, 52, 64, 64] }, + west: { uv: [56, 52, 60, 64] }, + up: { uv: [56, 52, 52, 48] }, + down: { uv: [60, 48, 56, 52] }, + model: 'steve', }, //Alex { - "name": "right_arm", - "size": [3, 12, 4], - "pos": [5.5, 17.5, 0], - "origin": [0, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [44, 20, 47, 32]}, - "east": {"uv": [40, 20, 44, 32]}, - "south": {"uv": [51, 20, 54, 32]}, - "west": {"uv": [47, 20, 51, 32]}, - "up": {"uv": [47, 20, 44, 16]}, - "down": {"uv": [50, 16, 47, 20]}, - "model": "alex" - }, - { - "name": "right_arm_layer", - "size": [3.5, 12.5, 4.5], - "pos": [5.5, 17.5, 0], - "origin": [0, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [44, 36, 47, 48]}, - "east": {"uv": [40, 36, 44, 48]}, - "south": {"uv": [51, 36, 54, 48]}, - "west": {"uv": [47, 36, 51, 48]}, - "up": {"uv": [47, 36, 44, 32]}, - "down": {"uv": [50, 32, 47, 36]}, - "model": "alex" - }, - { - "name": "left_arm", - "size": [3, 12, 4], - "pos": [-5.5, 17.5, 0], - "origin": [0, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [36, 52, 39, 64]}, - "east": {"uv": [32, 52, 36, 64]}, - "south": {"uv": [43, 52, 46, 64]}, - "west": {"uv": [39, 52, 43, 64]}, - "up": {"uv": [39, 52, 36, 48]}, - "down": {"uv": [42, 48, 39, 52]}, - "model": "alex" - }, - { - "name": "left_arm_layer", - "size": [3.5, 12.5, 4.5], - "pos": [-5.5, 17.5, 0], - "origin": [0, 22, 0], - "rotation": [22.5, 0, 0], - "north": {"uv": [52, 52, 55, 64]}, - "east": {"uv": [48, 52, 52, 64]}, - "south": {"uv": [59, 52, 62, 64]}, - "west": {"uv": [55, 52, 59, 64]}, - "up": {"uv": [55, 52, 52, 48]}, - "down": {"uv": [58, 48, 55, 52]}, - "model": "alex" - } - ] + name: 'right_arm', + size: [3, 12, 4], + pos: [5.5, 17.5, 0], + origin: [0, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [44, 20, 47, 32] }, + east: { uv: [40, 20, 44, 32] }, + south: { uv: [51, 20, 54, 32] }, + west: { uv: [47, 20, 51, 32] }, + up: { uv: [47, 20, 44, 16] }, + down: { uv: [50, 16, 47, 20] }, + model: 'alex', + }, + { + name: 'right_arm_layer', + size: [3.5, 12.5, 4.5], + pos: [5.5, 17.5, 0], + origin: [0, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [44, 36, 47, 48] }, + east: { uv: [40, 36, 44, 48] }, + south: { uv: [51, 36, 54, 48] }, + west: { uv: [47, 36, 51, 48] }, + up: { uv: [47, 36, 44, 32] }, + down: { uv: [50, 32, 47, 36] }, + model: 'alex', + }, + { + name: 'left_arm', + size: [3, 12, 4], + pos: [-5.5, 17.5, 0], + origin: [0, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [36, 52, 39, 64] }, + east: { uv: [32, 52, 36, 64] }, + south: { uv: [43, 52, 46, 64] }, + west: { uv: [39, 52, 43, 64] }, + up: { uv: [39, 52, 36, 48] }, + down: { uv: [42, 48, 39, 52] }, + model: 'alex', + }, + { + name: 'left_arm_layer', + size: [3.5, 12.5, 4.5], + pos: [-5.5, 17.5, 0], + origin: [0, 22, 0], + rotation: [22.5, 0, 0], + north: { uv: [52, 52, 55, 64] }, + east: { uv: [48, 52, 52, 64] }, + south: { uv: [59, 52, 62, 64] }, + west: { uv: [55, 52, 59, 64] }, + up: { uv: [55, 52, 52, 48] }, + down: { uv: [58, 48, 55, 52] }, + model: 'alex', + }, + ], }, armor_stand: { texture: 'assets/armor_stand.png', texture_size: [64, 64], elements: [ { - "size": [12, 1, 12], - "pos": [0, 0.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [12, 44, 24, 45]}, - "east": {"uv": [0, 44, 12, 45]}, - "south": {"uv": [36, 44, 48, 45]}, - "west": {"uv": [24, 44, 36, 45]}, - "up": {"uv": [24, 44, 12, 32]}, - "down": {"uv": [36, 32, 24, 44]} - }, - { - "size": [12, 3, 3], - "pos": [0, 22.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [3, 29, 15, 32]}, - "east": {"uv": [0, 29, 3, 32]}, - "south": {"uv": [18, 29, 30, 32]}, - "west": {"uv": [15, 29, 18, 32]}, - "up": {"uv": [15, 29, 3, 26]}, - "down": {"uv": [27, 26, 15, 29]} - }, - { - "size": [2, 7, 2], - "pos": [2, 17.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [18, 2, 20, 9]}, - "east": {"uv": [16, 2, 18, 9]}, - "south": {"uv": [22, 2, 24, 9]}, - "west": {"uv": [20, 2, 22, 9]}, - "up": {"uv": [20, 2, 18, 0]}, - "down": {"uv": [22, 0, 20, 2]} - }, - { - "size": [2, 7, 2], - "pos": [-2, 17.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [50, 18, 52, 25]}, - "east": {"uv": [48, 18, 50, 25]}, - "south": {"uv": [54, 18, 56, 25]}, - "west": {"uv": [52, 18, 54, 25]}, - "up": {"uv": [52, 18, 50, 16]}, - "down": {"uv": [54, 16, 52, 18]} - }, - { - "size": [8, 2, 2], - "pos": [0, 13, 0], - "origin": [0, 0, 0], - "north": {"uv": [2, 50, 10, 52]}, - "east": {"uv": [0, 50, 2, 52]}, - "south": {"uv": [12, 50, 20, 52]}, - "west": {"uv": [10, 50, 12, 52]}, - "up": {"uv": [10, 50, 2, 48]}, - "down": {"uv": [18, 48, 10, 50]} - }, - { - "size": [2, 7, 2], - "pos": [0, 26.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [2, 2, 4, 9]}, - "east": {"uv": [0, 2, 2, 9]}, - "south": {"uv": [6, 2, 8, 9]}, - "west": {"uv": [4, 2, 6, 9]}, - "up": {"uv": [4, 2, 2, 0]}, - "down": {"uv": [6, 0, 4, 2]} - }, - { - "size": [2, 12, 2], - "pos": [-6, 18, 0], - "origin": [0, 0, 0], - "north": {"uv": [36, 18, 34, 30]}, - "east": {"uv": [38, 18, 36, 30]}, - "south": {"uv": [40, 18, 38, 30]}, - "west": {"uv": [34, 18, 32, 30]}, - "up": {"uv": [34, 18, 36, 16]}, - "down": {"uv": [36, 16, 38, 18]} - }, - { - "size": [2, 11, 2], - "pos": [-1.9, 6.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [44, 18, 42, 29]}, - "east": {"uv": [46, 18, 44, 29]}, - "south": {"uv": [48, 18, 46, 29]}, - "west": {"uv": [42, 18, 40, 29]}, - "up": {"uv": [42, 18, 44, 16]}, - "down": {"uv": [44, 16, 46, 18]} - }, - { - "size": [2, 12, 2], - "pos": [6, 18, 0], - "origin": [0, 0, 0], - "north": {"uv": [26, 2, 28, 14]}, - "east": {"uv": [24, 2, 26, 14]}, - "south": {"uv": [30, 2, 32, 14]}, - "west": {"uv": [28, 2, 30, 14]}, - "up": {"uv": [28, 2, 26, 0]}, - "down": {"uv": [30, 0, 28, 2]} - }, - { - "size": [2, 11, 2], - "pos": [1.9, 6.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [10, 2, 12, 13]}, - "east": {"uv": [8, 2, 10, 13]}, - "south": {"uv": [14, 2, 16, 13]}, - "west": {"uv": [12, 2, 14, 13]}, - "up": {"uv": [12, 2, 10, 0]}, - "down": {"uv": [14, 0, 12, 2]} - } + size: [12, 1, 12], + pos: [0, 0.5, 0], + origin: [0, 0, 0], + north: { uv: [12, 44, 24, 45] }, + east: { uv: [0, 44, 12, 45] }, + south: { uv: [36, 44, 48, 45] }, + west: { uv: [24, 44, 36, 45] }, + up: { uv: [24, 44, 12, 32] }, + down: { uv: [36, 32, 24, 44] }, + }, + { + size: [12, 3, 3], + pos: [0, 22.5, 0], + origin: [0, 0, 0], + north: { uv: [3, 29, 15, 32] }, + east: { uv: [0, 29, 3, 32] }, + south: { uv: [18, 29, 30, 32] }, + west: { uv: [15, 29, 18, 32] }, + up: { uv: [15, 29, 3, 26] }, + down: { uv: [27, 26, 15, 29] }, + }, + { + size: [2, 7, 2], + pos: [2, 17.5, 0], + origin: [0, 0, 0], + north: { uv: [18, 2, 20, 9] }, + east: { uv: [16, 2, 18, 9] }, + south: { uv: [22, 2, 24, 9] }, + west: { uv: [20, 2, 22, 9] }, + up: { uv: [20, 2, 18, 0] }, + down: { uv: [22, 0, 20, 2] }, + }, + { + size: [2, 7, 2], + pos: [-2, 17.5, 0], + origin: [0, 0, 0], + north: { uv: [50, 18, 52, 25] }, + east: { uv: [48, 18, 50, 25] }, + south: { uv: [54, 18, 56, 25] }, + west: { uv: [52, 18, 54, 25] }, + up: { uv: [52, 18, 50, 16] }, + down: { uv: [54, 16, 52, 18] }, + }, + { + size: [8, 2, 2], + pos: [0, 13, 0], + origin: [0, 0, 0], + north: { uv: [2, 50, 10, 52] }, + east: { uv: [0, 50, 2, 52] }, + south: { uv: [12, 50, 20, 52] }, + west: { uv: [10, 50, 12, 52] }, + up: { uv: [10, 50, 2, 48] }, + down: { uv: [18, 48, 10, 50] }, + }, + { + size: [2, 7, 2], + pos: [0, 26.5, 0], + origin: [0, 0, 0], + north: { uv: [2, 2, 4, 9] }, + east: { uv: [0, 2, 2, 9] }, + south: { uv: [6, 2, 8, 9] }, + west: { uv: [4, 2, 6, 9] }, + up: { uv: [4, 2, 2, 0] }, + down: { uv: [6, 0, 4, 2] }, + }, + { + size: [2, 12, 2], + pos: [-6, 18, 0], + origin: [0, 0, 0], + north: { uv: [36, 18, 34, 30] }, + east: { uv: [38, 18, 36, 30] }, + south: { uv: [40, 18, 38, 30] }, + west: { uv: [34, 18, 32, 30] }, + up: { uv: [34, 18, 36, 16] }, + down: { uv: [36, 16, 38, 18] }, + }, + { + size: [2, 11, 2], + pos: [-1.9, 6.5, 0], + origin: [0, 0, 0], + north: { uv: [44, 18, 42, 29] }, + east: { uv: [46, 18, 44, 29] }, + south: { uv: [48, 18, 46, 29] }, + west: { uv: [42, 18, 40, 29] }, + up: { uv: [42, 18, 44, 16] }, + down: { uv: [44, 16, 46, 18] }, + }, + { + size: [2, 12, 2], + pos: [6, 18, 0], + origin: [0, 0, 0], + north: { uv: [26, 2, 28, 14] }, + east: { uv: [24, 2, 26, 14] }, + south: { uv: [30, 2, 32, 14] }, + west: { uv: [28, 2, 30, 14] }, + up: { uv: [28, 2, 26, 0] }, + down: { uv: [30, 0, 28, 2] }, + }, + { + size: [2, 11, 2], + pos: [1.9, 6.5, 0], + origin: [0, 0, 0], + north: { uv: [10, 2, 12, 13] }, + east: { uv: [8, 2, 10, 13] }, + south: { uv: [14, 2, 16, 13] }, + west: { uv: [12, 2, 14, 13] }, + up: { uv: [12, 2, 10, 0] }, + down: { uv: [14, 0, 12, 2] }, + }, ], }, armor_stand_small: { @@ -345,115 +345,115 @@ const DisplayReferences = { texture_size: [64, 64], elements: [ { - "size": [6, 0.5, 6], - "pos": [0, 0.25, 0], - "origin": [0, 0, 0], - "north": {"uv": [12, 44, 24, 45]}, - "east": {"uv": [0, 44, 12, 45]}, - "south": {"uv": [36, 44, 48, 45]}, - "west": {"uv": [24, 44, 36, 45]}, - "up": {"uv": [24, 44, 12, 32]}, - "down": {"uv": [36, 32, 24, 44]} - }, - { - "size": [6, 1.5, 1.5], - "pos": [0, 11.25, 0], - "origin": [0, 0, 0], - "north": {"uv": [3, 29, 15, 32]}, - "east": {"uv": [0, 29, 3, 32]}, - "south": {"uv": [18, 29, 30, 32]}, - "west": {"uv": [15, 29, 18, 32]}, - "up": {"uv": [15, 29, 3, 26]}, - "down": {"uv": [27, 26, 15, 29]} - }, - { - "size": [1, 3.5, 1], - "pos": [1, 8.75, 0], - "origin": [0, 0, 0], - "north": {"uv": [18, 2, 20, 9]}, - "east": {"uv": [16, 2, 18, 9]}, - "south": {"uv": [22, 2, 24, 9]}, - "west": {"uv": [20, 2, 22, 9]}, - "up": {"uv": [20, 2, 18, 0]}, - "down": {"uv": [22, 0, 20, 2]} - }, - { - "size": [1, 3.5, 1], - "pos": [-1, 8.75, 0], - "origin": [0, 0, 0], - "north": {"uv": [50, 18, 52, 25]}, - "east": {"uv": [48, 18, 50, 25]}, - "south": {"uv": [54, 18, 56, 25]}, - "west": {"uv": [52, 18, 54, 25]}, - "up": {"uv": [52, 18, 50, 16]}, - "down": {"uv": [54, 16, 52, 18]} - }, - { - "size": [4, 1, 1], - "pos": [0, 6.5, 0], - "origin": [0, 0, 0], - "north": {"uv": [2, 50, 10, 52]}, - "east": {"uv": [0, 50, 2, 52]}, - "south": {"uv": [12, 50, 20, 52]}, - "west": {"uv": [10, 50, 12, 52]}, - "up": {"uv": [10, 50, 2, 48]}, - "down": {"uv": [18, 48, 10, 50]} - }, - { - "size": [1.5, 5.25, 1.48], - "pos": [0, 13.875, 0], - "origin": [0, 12, 0], - "north": {"uv": [2, 2, 4, 9]}, - "east": {"uv": [0, 2, 2, 9]}, - "south": {"uv": [6, 2, 8, 9]}, - "west": {"uv": [4, 2, 6, 9]}, - "up": {"uv": [4, 2, 2, 0]}, - "down": {"uv": [6, 0, 4, 2]} - }, - { - "size": [1, 6, 1], - "pos": [-3, 9, 0], - "origin": [0, 0, 0], - "north": {"uv": [36, 18, 34, 30]}, - "east": {"uv": [38, 18, 36, 30]}, - "south": {"uv": [40, 18, 38, 30]}, - "west": {"uv": [34, 18, 32, 30]}, - "up": {"uv": [34, 18, 36, 16]}, - "down": {"uv": [36, 16, 38, 18]} - }, - { - "size": [1, 5.5, 1], - "pos": [-0.95, 3.25, 0], - "origin": [0, 0, 0], - "north": {"uv": [44, 18, 42, 29]}, - "east": {"uv": [46, 18, 44, 29]}, - "south": {"uv": [48, 18, 46, 29]}, - "west": {"uv": [42, 18, 40, 29]}, - "up": {"uv": [42, 18, 44, 16]}, - "down": {"uv": [44, 16, 46, 18]} - }, - { - "size": [1, 6, 1], - "pos": [3, 9, 0], - "origin": [0, 0, 0], - "north": {"uv": [26, 2, 28, 14]}, - "east": {"uv": [24, 2, 26, 14]}, - "south": {"uv": [30, 2, 32, 14]}, - "west": {"uv": [28, 2, 30, 14]}, - "up": {"uv": [28, 2, 26, 0]}, - "down": {"uv": [30, 0, 28, 2]} - }, - { - "size": [1, 5.5, 1], - "pos": [0.95, 3.25, 0], - "origin": [0, 0, 0], - "north": {"uv": [10, 2, 12, 13]}, - "east": {"uv": [8, 2, 10, 13]}, - "south": {"uv": [14, 2, 16, 13]}, - "west": {"uv": [12, 2, 14, 13]}, - "up": {"uv": [12, 2, 10, 0]}, - "down": {"uv": [14, 0, 12, 2]} - } + size: [6, 0.5, 6], + pos: [0, 0.25, 0], + origin: [0, 0, 0], + north: { uv: [12, 44, 24, 45] }, + east: { uv: [0, 44, 12, 45] }, + south: { uv: [36, 44, 48, 45] }, + west: { uv: [24, 44, 36, 45] }, + up: { uv: [24, 44, 12, 32] }, + down: { uv: [36, 32, 24, 44] }, + }, + { + size: [6, 1.5, 1.5], + pos: [0, 11.25, 0], + origin: [0, 0, 0], + north: { uv: [3, 29, 15, 32] }, + east: { uv: [0, 29, 3, 32] }, + south: { uv: [18, 29, 30, 32] }, + west: { uv: [15, 29, 18, 32] }, + up: { uv: [15, 29, 3, 26] }, + down: { uv: [27, 26, 15, 29] }, + }, + { + size: [1, 3.5, 1], + pos: [1, 8.75, 0], + origin: [0, 0, 0], + north: { uv: [18, 2, 20, 9] }, + east: { uv: [16, 2, 18, 9] }, + south: { uv: [22, 2, 24, 9] }, + west: { uv: [20, 2, 22, 9] }, + up: { uv: [20, 2, 18, 0] }, + down: { uv: [22, 0, 20, 2] }, + }, + { + size: [1, 3.5, 1], + pos: [-1, 8.75, 0], + origin: [0, 0, 0], + north: { uv: [50, 18, 52, 25] }, + east: { uv: [48, 18, 50, 25] }, + south: { uv: [54, 18, 56, 25] }, + west: { uv: [52, 18, 54, 25] }, + up: { uv: [52, 18, 50, 16] }, + down: { uv: [54, 16, 52, 18] }, + }, + { + size: [4, 1, 1], + pos: [0, 6.5, 0], + origin: [0, 0, 0], + north: { uv: [2, 50, 10, 52] }, + east: { uv: [0, 50, 2, 52] }, + south: { uv: [12, 50, 20, 52] }, + west: { uv: [10, 50, 12, 52] }, + up: { uv: [10, 50, 2, 48] }, + down: { uv: [18, 48, 10, 50] }, + }, + { + size: [1.5, 5.25, 1.48], + pos: [0, 13.875, 0], + origin: [0, 12, 0], + north: { uv: [2, 2, 4, 9] }, + east: { uv: [0, 2, 2, 9] }, + south: { uv: [6, 2, 8, 9] }, + west: { uv: [4, 2, 6, 9] }, + up: { uv: [4, 2, 2, 0] }, + down: { uv: [6, 0, 4, 2] }, + }, + { + size: [1, 6, 1], + pos: [-3, 9, 0], + origin: [0, 0, 0], + north: { uv: [36, 18, 34, 30] }, + east: { uv: [38, 18, 36, 30] }, + south: { uv: [40, 18, 38, 30] }, + west: { uv: [34, 18, 32, 30] }, + up: { uv: [34, 18, 36, 16] }, + down: { uv: [36, 16, 38, 18] }, + }, + { + size: [1, 5.5, 1], + pos: [-0.95, 3.25, 0], + origin: [0, 0, 0], + north: { uv: [44, 18, 42, 29] }, + east: { uv: [46, 18, 44, 29] }, + south: { uv: [48, 18, 46, 29] }, + west: { uv: [42, 18, 40, 29] }, + up: { uv: [42, 18, 44, 16] }, + down: { uv: [44, 16, 46, 18] }, + }, + { + size: [1, 6, 1], + pos: [3, 9, 0], + origin: [0, 0, 0], + north: { uv: [26, 2, 28, 14] }, + east: { uv: [24, 2, 26, 14] }, + south: { uv: [30, 2, 32, 14] }, + west: { uv: [28, 2, 30, 14] }, + up: { uv: [28, 2, 26, 0] }, + down: { uv: [30, 0, 28, 2] }, + }, + { + size: [1, 5.5, 1], + pos: [0.95, 3.25, 0], + origin: [0, 0, 0], + north: { uv: [10, 2, 12, 13] }, + east: { uv: [8, 2, 10, 13] }, + south: { uv: [14, 2, 16, 13] }, + west: { uv: [12, 2, 14, 13] }, + up: { uv: [12, 2, 10, 0] }, + down: { uv: [14, 0, 12, 2] }, + }, ], }, fox: { @@ -461,253 +461,456 @@ const DisplayReferences = { texture_size: [32, 32], elements: [ { - "size": [8, 6, 6], - "pos": [0, 4, 0], - "origin": [0, 0, 0], - "north": {"uv": [7, 11, 15, 17]}, - "east": {"uv": [1, 11, 7, 17]}, - "south": {"uv": [21, 11, 29, 17]}, - "west": {"uv": [15, 11, 21, 17]}, - "up": {"uv": [15, 11, 7, 5]}, - "down": {"uv": [23, 5, 15, 11]} - }, - { - "size": [4, 2, 3], - "pos": [0, 2, -4.5], - "origin": [0, 0, 0], - "north": {"uv": [9, 21, 13, 23]}, - "east": {"uv": [6, 21, 9, 23]}, - "south": {"uv": [16, 21, 20, 23]}, - "west": {"uv": [13, 21, 16, 23]}, - "up": {"uv": [13, 21, 9, 18]}, - "down": {"uv": [17, 18, 13, 21]} - }, - { - "size": [2, 2, 1], - "pos": [3, 8, -1.5], - "origin": [0, 0, 0], - "north": {"uv": [9, 2, 11, 4]}, - "east": {"uv": [8, 2, 9, 4]}, - "south": {"uv": [12, 2, 14, 4]}, - "west": {"uv": [11, 2, 12, 4]}, - "up": {"uv": [11, 2, 9, 1]}, - "down": {"uv": [13, 1, 11, 2]} - }, - { - "size": [2, 2, 1], - "pos": [-3, 8, -1.5], - "origin": [0, 0, 0], - "north": {"uv": [16, 2, 18, 4]}, - "east": {"uv": [15, 2, 16, 4]}, - "south": {"uv": [19, 2, 21, 4]}, - "west": {"uv": [18, 2, 19, 4]}, - "up": {"uv": [18, 2, 16, 1]}, - "down": {"uv": [20, 1, 18, 2]} - } - - ] + size: [8, 6, 6], + pos: [0, 4, 0], + origin: [0, 0, 0], + north: { uv: [7, 11, 15, 17] }, + east: { uv: [1, 11, 7, 17] }, + south: { uv: [21, 11, 29, 17] }, + west: { uv: [15, 11, 21, 17] }, + up: { uv: [15, 11, 7, 5] }, + down: { uv: [23, 5, 15, 11] }, + }, + { + size: [4, 2, 3], + pos: [0, 2, -4.5], + origin: [0, 0, 0], + north: { uv: [9, 21, 13, 23] }, + east: { uv: [6, 21, 9, 23] }, + south: { uv: [16, 21, 20, 23] }, + west: { uv: [13, 21, 16, 23] }, + up: { uv: [13, 21, 9, 18] }, + down: { uv: [17, 18, 13, 21] }, + }, + { + size: [2, 2, 1], + pos: [3, 8, -1.5], + origin: [0, 0, 0], + north: { uv: [9, 2, 11, 4] }, + east: { uv: [8, 2, 9, 4] }, + south: { uv: [12, 2, 14, 4] }, + west: { uv: [11, 2, 12, 4] }, + up: { uv: [11, 2, 9, 1] }, + down: { uv: [13, 1, 11, 2] }, + }, + { + size: [2, 2, 1], + pos: [-3, 8, -1.5], + origin: [0, 0, 0], + north: { uv: [16, 2, 18, 4] }, + east: { uv: [15, 2, 16, 4] }, + south: { uv: [19, 2, 21, 4] }, + west: { uv: [18, 2, 19, 4] }, + up: { uv: [18, 2, 16, 1] }, + down: { uv: [20, 1, 18, 2] }, + }, + ], }, zombie: { texture: 'assets/zombie.png', elements: [ { - "size": [4, 12, 4], - "pos": [0, 0, -2], - "origin": [0, 0, 0], - "north": {"uv": [0.01, 5.01, 0.99, 7.99], "texture": "#1"}, - "east": {"uv": [3.01, 5.01, 3.99, 7.99], "texture": "#1"}, - "south": {"uv": [2.01, 5.01, 2.99, 7.99], "texture": "#1"}, - "west": {"uv": [1.01, 5.01, 1.99, 7.99], "texture": "#1"}, - "up": {"uv": [1.01, 4.01, 1.99, 4.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [2.01, 4.01, 2.99, 4.99], "texture": "#1", "rotation": 90} - }, - { - "size": [4, 12, 4], - "pos": [0, 0, 2], - "origin": [0, 0, 0], - "north": {"uv": [4.01, 13.01, 4.99, 15.99], "texture": "#1"}, - "east": {"uv": [8.01, 13.01, 6.99, 15.99], "texture": "#1"}, - "south": {"uv": [6.01, 13.01, 6.99, 15.99], "texture": "#1"}, - "west": {"uv": [5.01, 13.01, 5.99, 15.99], "texture": "#1"}, - "up": {"uv": [5.01, 12.01, 5.99, 12.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [6.01, 12.01, 6.99, 12.99], "texture": "#1", "rotation": 90} - }, - { - "size": [4, 12, 8], - "pos": [0, 12, 0], - "origin": [0, 0, 0], - "north": {"uv": [4.01, 5.01, 4.99, 7.99], "texture": "#1"}, - "east": {"uv": [8.01, 5.01, 9.99, 7.99], "texture": "#1"}, - "south": {"uv": [7.01, 5.01, 7.99, 7.99], "texture": "#1"}, - "west": {"uv": [5.01, 5.01, 6.99, 7.99], "texture": "#1"}, - "up": {"uv": [5.01, 4.01, 6.99, 4.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [7.01, 4.01, 8.99, 4.99], "texture": "#1", "rotation": 270} - }, - { - "size": [8, 8, 8], - "pos": [0, 22, 0], - "origin": [0, 0, 0], - "north": {"uv": [0.01, 2.01, 1.99, 3.99], "texture": "#1"}, - "east": {"uv": [6.01, 2.01, 7.99, 3.99], "texture": "#1"}, - "south": {"uv": [4.01, 2.01, 5.99, 3.99], "texture": "#1"}, - "west": {"uv": [2.01, 2.01, 3.99, 3.99], "texture": "#1"}, - "up": {"uv": [2.01, 0.01, 3.99, 1.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [4.01, 0.01, 5.99, 1.99], "texture": "#1", "rotation": 90} - }, - { - "size": [12, 4, 4], - "pos": [-4, 16, -6], - "origin": [0, 0, 0], - "north": {"uv": [12.01, 5.01, 12.99, 7.99], "texture": "#1", "rotation": 270}, - "east": {"uv": [11.01, 4.01, 11.99, 4.99], "texture": "#1", "rotation": 180}, - "south": {"uv": [10.01, 5.01, 10.99, 7.99], "texture": "#1", "rotation": 90}, - "west": {"uv": [12.01, 4.01, 12.99, 4.99], "texture": "#1", "rotation": 180}, - "up": {"uv": [11.01, 5.01, 11.99, 7.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [13.01, 5.01, 13.99, 7.99], "texture": "#1", "rotation": 90} - }, - { - "size": [12, 4, 4], - "pos": [-4, 16, 6], - "origin": [0, 0, 0], - "north": {"uv": [10.01, 13.01, 10.99, 15.99], "texture": "#1", "rotation": 270}, - "east": {"uv": [9.01, 12.01, 9.99, 12.99], "texture": "#1", "rotation": 180}, - "south": {"uv": [8.01, 13.01, 8.99, 15.99], "texture": "#1", "rotation": 90}, - "west": {"uv": [10.01, 12.01, 10.99, 12.99], "texture": "#1", "rotation": 180}, - "up": {"uv": [9.01, 13.01, 9.99, 15.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [11.01, 13.01, 11.99, 15.99], "texture": "#1", "rotation": 90} - } - ] + size: [4, 12, 4], + pos: [0, 0, -2], + origin: [0, 0, 0], + north: { uv: [0.01, 5.01, 0.99, 7.99], texture: '#1' }, + east: { uv: [3.01, 5.01, 3.99, 7.99], texture: '#1' }, + south: { uv: [2.01, 5.01, 2.99, 7.99], texture: '#1' }, + west: { uv: [1.01, 5.01, 1.99, 7.99], texture: '#1' }, + up: { uv: [1.01, 4.01, 1.99, 4.99], texture: '#1', rotation: 90 }, + down: { uv: [2.01, 4.01, 2.99, 4.99], texture: '#1', rotation: 90 }, + }, + { + size: [4, 12, 4], + pos: [0, 0, 2], + origin: [0, 0, 0], + north: { uv: [4.01, 13.01, 4.99, 15.99], texture: '#1' }, + east: { uv: [8.01, 13.01, 6.99, 15.99], texture: '#1' }, + south: { uv: [6.01, 13.01, 6.99, 15.99], texture: '#1' }, + west: { uv: [5.01, 13.01, 5.99, 15.99], texture: '#1' }, + up: { uv: [5.01, 12.01, 5.99, 12.99], texture: '#1', rotation: 90 }, + down: { uv: [6.01, 12.01, 6.99, 12.99], texture: '#1', rotation: 90 }, + }, + { + size: [4, 12, 8], + pos: [0, 12, 0], + origin: [0, 0, 0], + north: { uv: [4.01, 5.01, 4.99, 7.99], texture: '#1' }, + east: { uv: [8.01, 5.01, 9.99, 7.99], texture: '#1' }, + south: { uv: [7.01, 5.01, 7.99, 7.99], texture: '#1' }, + west: { uv: [5.01, 5.01, 6.99, 7.99], texture: '#1' }, + up: { uv: [5.01, 4.01, 6.99, 4.99], texture: '#1', rotation: 90 }, + down: { uv: [7.01, 4.01, 8.99, 4.99], texture: '#1', rotation: 270 }, + }, + { + size: [8, 8, 8], + pos: [0, 22, 0], + origin: [0, 0, 0], + north: { uv: [0.01, 2.01, 1.99, 3.99], texture: '#1' }, + east: { uv: [6.01, 2.01, 7.99, 3.99], texture: '#1' }, + south: { uv: [4.01, 2.01, 5.99, 3.99], texture: '#1' }, + west: { uv: [2.01, 2.01, 3.99, 3.99], texture: '#1' }, + up: { uv: [2.01, 0.01, 3.99, 1.99], texture: '#1', rotation: 90 }, + down: { uv: [4.01, 0.01, 5.99, 1.99], texture: '#1', rotation: 90 }, + }, + { + size: [12, 4, 4], + pos: [-4, 16, -6], + origin: [0, 0, 0], + north: { uv: [12.01, 5.01, 12.99, 7.99], texture: '#1', rotation: 270 }, + east: { uv: [11.01, 4.01, 11.99, 4.99], texture: '#1', rotation: 180 }, + south: { uv: [10.01, 5.01, 10.99, 7.99], texture: '#1', rotation: 90 }, + west: { uv: [12.01, 4.01, 12.99, 4.99], texture: '#1', rotation: 180 }, + up: { uv: [11.01, 5.01, 11.99, 7.99], texture: '#1', rotation: 90 }, + down: { uv: [13.01, 5.01, 13.99, 7.99], texture: '#1', rotation: 90 }, + }, + { + size: [12, 4, 4], + pos: [-4, 16, 6], + origin: [0, 0, 0], + north: { uv: [10.01, 13.01, 10.99, 15.99], texture: '#1', rotation: 270 }, + east: { uv: [9.01, 12.01, 9.99, 12.99], texture: '#1', rotation: 180 }, + south: { uv: [8.01, 13.01, 8.99, 15.99], texture: '#1', rotation: 90 }, + west: { uv: [10.01, 12.01, 10.99, 12.99], texture: '#1', rotation: 180 }, + up: { uv: [9.01, 13.01, 9.99, 15.99], texture: '#1', rotation: 90 }, + down: { uv: [11.01, 13.01, 11.99, 15.99], texture: '#1', rotation: 90 }, + }, + ], }, baby_zombie: { texture: 'assets/zombie.png', elements: [ { - "size": [2, 6, 2], - "pos": [-2.220446049250313e-16, -3, -1], - "origin": [0, 0, 0], - "north": {"uv": [0.01, 5.01, 0.99, 7.99], "texture": "#1"}, - "east": {"uv": [3.01, 5.01, 3.99, 7.99], "texture": "#1"}, - "south": {"uv": [2.01, 5.01, 2.99, 7.99], "texture": "#1"}, - "west": {"uv": [1.01, 5.01, 1.99, 7.99], "texture": "#1"}, - "up": {"uv": [1.01, 4.01, 1.99, 4.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [2.01, 4.01, 2.99, 4.99], "texture": "#1", "rotation": 90} - }, - { - "size": [2, 6, 2], - "pos": [-2.220446049250313e-16, -3, 1], - "origin": [0, 0, 0], - "north": {"uv": [4.01, 13.01, 4.99, 15.99], "texture": "#1"}, - "east": {"uv": [8.01, 13.01, 6.99, 15.99], "texture": "#1"}, - "south": {"uv": [6.01, 13.01, 6.99, 15.99], "texture": "#1"}, - "west": {"uv": [5.01, 13.01, 5.99, 15.99], "texture": "#1"}, - "up": {"uv": [5.01, 12.01, 5.99, 12.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [6.01, 12.01, 6.99, 12.99], "texture": "#1", "rotation": 90} - }, - { - "size": [2, 6, 4], - "pos": [-2.220446049250313e-16, 3, 0], - "origin": [0, 0, 0], - "north": {"uv": [4.01, 5.01, 4.99, 7.99], "texture": "#1"}, - "east": {"uv": [8.01, 5.01, 9.99, 7.99], "texture": "#1"}, - "south": {"uv": [7.01, 5.01, 7.99, 7.99], "texture": "#1"}, - "west": {"uv": [5.01, 5.01, 6.99, 7.99], "texture": "#1"}, - "up": {"uv": [5.01, 4.01, 6.99, 4.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [7.01, 4.01, 8.99, 4.99], "texture": "#1", "rotation": 270} - }, - { - "size": [6.0, 6.0, 6.0], - "pos": [0, 9, 0], - "origin": [0, 9, 0], - "north": {"uv": [0.01, 2.01, 1.99, 3.99], "texture": "#1"}, - "east": {"uv": [6.01, 2.01, 7.99, 3.99], "texture": "#1"}, - "south": {"uv": [4.01, 2.01, 5.99, 3.99], "texture": "#1"}, - "west": {"uv": [2.01, 2.01, 3.99, 3.99], "texture": "#1"}, - "up": {"uv": [2.01, 0.01, 3.99, 1.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [4.01, 0.01, 5.99, 1.99], "texture": "#1", "rotation": 90} - }, - { - "size": [6, 2, 2], - "pos": [-2.000000000000001, 5, -3], - "origin": [0, 0, 0], - "north": {"uv": [12.01, 5.01, 12.99, 7.99], "texture": "#1", "rotation": 270}, - "east": {"uv": [11.01, 4.01, 11.99, 4.99], "texture": "#1", "rotation": 180}, - "south": {"uv": [10.01, 5.01, 10.99, 7.99], "texture": "#1", "rotation": 90}, - "west": {"uv": [12.01, 4.01, 12.99, 4.99], "texture": "#1", "rotation": 180}, - "up": {"uv": [11.01, 5.01, 11.99, 7.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [13.01, 5.01, 13.99, 7.99], "texture": "#1", "rotation": 90} - }, - { - "size": [6, 2, 2], - "pos": [-2.000000000000001, 5, 3], - "origin": [0, 0, 0], - "north": {"uv": [10.01, 13.01, 10.99, 15.99], "texture": "#1", "rotation": 270}, - "east": {"uv": [9.01, 12.01, 9.99, 12.99], "texture": "#1", "rotation": 180}, - "south": {"uv": [8.01, 13.01, 8.99, 15.99], "texture": "#1", "rotation": 90}, - "west": {"uv": [10.01, 12.01, 10.99, 12.99], "texture": "#1", "rotation": 180}, - "up": {"uv": [9.01, 13.01, 9.99, 15.99], "texture": "#1", "rotation": 90}, - "down": {"uv": [11.01, 13.01, 11.99, 15.99], "texture": "#1", "rotation": 90} - } + size: [2, 6, 2], + pos: [-2.220446049250313e-16, -3, -1], + origin: [0, 0, 0], + north: { uv: [0.01, 5.01, 0.99, 7.99], texture: '#1' }, + east: { uv: [3.01, 5.01, 3.99, 7.99], texture: '#1' }, + south: { uv: [2.01, 5.01, 2.99, 7.99], texture: '#1' }, + west: { uv: [1.01, 5.01, 1.99, 7.99], texture: '#1' }, + up: { uv: [1.01, 4.01, 1.99, 4.99], texture: '#1', rotation: 90 }, + down: { uv: [2.01, 4.01, 2.99, 4.99], texture: '#1', rotation: 90 }, + }, + { + size: [2, 6, 2], + pos: [-2.220446049250313e-16, -3, 1], + origin: [0, 0, 0], + north: { uv: [4.01, 13.01, 4.99, 15.99], texture: '#1' }, + east: { uv: [8.01, 13.01, 6.99, 15.99], texture: '#1' }, + south: { uv: [6.01, 13.01, 6.99, 15.99], texture: '#1' }, + west: { uv: [5.01, 13.01, 5.99, 15.99], texture: '#1' }, + up: { uv: [5.01, 12.01, 5.99, 12.99], texture: '#1', rotation: 90 }, + down: { uv: [6.01, 12.01, 6.99, 12.99], texture: '#1', rotation: 90 }, + }, + { + size: [2, 6, 4], + pos: [-2.220446049250313e-16, 3, 0], + origin: [0, 0, 0], + north: { uv: [4.01, 5.01, 4.99, 7.99], texture: '#1' }, + east: { uv: [8.01, 5.01, 9.99, 7.99], texture: '#1' }, + south: { uv: [7.01, 5.01, 7.99, 7.99], texture: '#1' }, + west: { uv: [5.01, 5.01, 6.99, 7.99], texture: '#1' }, + up: { uv: [5.01, 4.01, 6.99, 4.99], texture: '#1', rotation: 90 }, + down: { uv: [7.01, 4.01, 8.99, 4.99], texture: '#1', rotation: 270 }, + }, + { + size: [6.0, 6.0, 6.0], + pos: [0, 9, 0], + origin: [0, 9, 0], + north: { uv: [0.01, 2.01, 1.99, 3.99], texture: '#1' }, + east: { uv: [6.01, 2.01, 7.99, 3.99], texture: '#1' }, + south: { uv: [4.01, 2.01, 5.99, 3.99], texture: '#1' }, + west: { uv: [2.01, 2.01, 3.99, 3.99], texture: '#1' }, + up: { uv: [2.01, 0.01, 3.99, 1.99], texture: '#1', rotation: 90 }, + down: { uv: [4.01, 0.01, 5.99, 1.99], texture: '#1', rotation: 90 }, + }, + { + size: [6, 2, 2], + pos: [-2.000000000000001, 5, -3], + origin: [0, 0, 0], + north: { uv: [12.01, 5.01, 12.99, 7.99], texture: '#1', rotation: 270 }, + east: { uv: [11.01, 4.01, 11.99, 4.99], texture: '#1', rotation: 180 }, + south: { uv: [10.01, 5.01, 10.99, 7.99], texture: '#1', rotation: 90 }, + west: { uv: [12.01, 4.01, 12.99, 4.99], texture: '#1', rotation: 180 }, + up: { uv: [11.01, 5.01, 11.99, 7.99], texture: '#1', rotation: 90 }, + down: { uv: [13.01, 5.01, 13.99, 7.99], texture: '#1', rotation: 90 }, + }, + { + size: [6, 2, 2], + pos: [-2.000000000000001, 5, 3], + origin: [0, 0, 0], + north: { uv: [10.01, 13.01, 10.99, 15.99], texture: '#1', rotation: 270 }, + east: { uv: [9.01, 12.01, 9.99, 12.99], texture: '#1', rotation: 180 }, + south: { uv: [8.01, 13.01, 8.99, 15.99], texture: '#1', rotation: 90 }, + west: { uv: [10.01, 12.01, 10.99, 12.99], texture: '#1', rotation: 180 }, + up: { uv: [9.01, 13.01, 9.99, 15.99], texture: '#1', rotation: 90 }, + down: { uv: [11.01, 13.01, 11.99, 15.99], texture: '#1', rotation: 90 }, + }, ], }, monitor: { texture: 'black', elements: [ - {"size": [8, 8, 0.1], "pos": [0, 28.93, 31.20], "origin": [0, 0, 0], "north":{"uv":[0,0,0,0]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}}, - {"size": [8, 8, 0.1], "pos": [0, 19.07, 31.20], "origin": [0, 0, 0], "north":{"uv":[0,0,0,0]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}}, - {"size": [8, 8, 0.1], "pos": [5.65, 24, 31.2], "origin": [0, 0, 0], "north":{"uv":[0,0,0,0]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}}, - {"size": [8, 8, 0.1], "pos": [-5.65, 24, 31.2], "origin": [0, 0, 0], "north":{"uv":[0,0,0,0]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}} - ] + { + size: [8, 8, 0.1], + pos: [0, 28.93, 31.2], + origin: [0, 0, 0], + north: { uv: [0, 0, 0, 0] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, + { + size: [8, 8, 0.1], + pos: [0, 19.07, 31.2], + origin: [0, 0, 0], + north: { uv: [0, 0, 0, 0] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, + { + size: [8, 8, 0.1], + pos: [5.65, 24, 31.2], + origin: [0, 0, 0], + north: { uv: [0, 0, 0, 0] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, + { + size: [8, 8, 0.1], + pos: [-5.65, 24, 31.2], + origin: [0, 0, 0], + north: { uv: [0, 0, 0, 0] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, + ], }, block: { texture: 'assets/missing.png', elements: [ - {"size": [16,16,16], "pos": [8, -7.98, 8], "origin": [0, 0, 0], "north":{"uv":[0,0,16,16]},"east":{"uv":[0,0,16,16]},"south":{"uv":[0,0,16,16]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,16,16]},"down":{"uv":[0,0,16,16]}} - ] + { + size: [16, 16, 16], + pos: [8, -7.98, 8], + origin: [0, 0, 0], + north: { uv: [0, 0, 16, 16] }, + east: { uv: [0, 0, 16, 16] }, + south: { uv: [0, 0, 16, 16] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 16, 16] }, + down: { uv: [0, 0, 16, 16] }, + }, + ], }, frame_block: { texture: 'assets/missing.png', elements: [ - {"size": [16,16,16], "pos": [8, 8, 8], "origin": [0, 0, 0], "north":{"uv":[0,0,16,16]},"east":{"uv":[0,0,16,16]},"south":{"uv":[0,0,16,16]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,16,16]},"down":{"uv":[0,0,16,16]}} - ] + { + size: [16, 16, 16], + pos: [8, 8, 8], + origin: [0, 0, 0], + north: { uv: [0, 0, 16, 16] }, + east: { uv: [0, 0, 16, 16] }, + south: { uv: [0, 0, 16, 16] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 16, 16] }, + down: { uv: [0, 0, 16, 16] }, + }, + ], }, frame: { texture: 'assets/item_frame.png', elements: [ - {"size": [10,10,0.5], "pos": [8, 8, -0.25], "origin": [0, 0, 0], "north":{"uv":[3,3,13,13]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,0,0]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}}, + { + size: [10, 10, 0.5], + pos: [8, 8, -0.25], + origin: [0, 0, 0], + north: { uv: [3, 3, 13, 13] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 0, 0] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, - {"size": [1,12,1], "pos": [13.5, 8, -0.5], "origin": [0, 0, 0], "north":{"uv":[2,2,3,14]},"east":{"uv":[2,2,3,14]},"south":{"uv":[2,2,3,14]},"west":{"uv":[2,2,3,14]},"up":{"uv":[2,2,3,3]},"down":{"uv":[2,2,3,3]}}, - {"size": [1,12,1], "pos": [2.5, 8, -0.5], "origin": [0, 0, 0], "north":{"uv":[2,2,3,14]},"east":{"uv":[2,2,3,14]},"south":{"uv":[2,2,3,14]},"west":{"uv":[2,2,3,14]},"up":{"uv":[2,2,3,3]},"down":{"uv":[2,2,3,3]}}, + { + size: [1, 12, 1], + pos: [13.5, 8, -0.5], + origin: [0, 0, 0], + north: { uv: [2, 2, 3, 14] }, + east: { uv: [2, 2, 3, 14] }, + south: { uv: [2, 2, 3, 14] }, + west: { uv: [2, 2, 3, 14] }, + up: { uv: [2, 2, 3, 3] }, + down: { uv: [2, 2, 3, 3] }, + }, + { + size: [1, 12, 1], + pos: [2.5, 8, -0.5], + origin: [0, 0, 0], + north: { uv: [2, 2, 3, 14] }, + east: { uv: [2, 2, 3, 14] }, + south: { uv: [2, 2, 3, 14] }, + west: { uv: [2, 2, 3, 14] }, + up: { uv: [2, 2, 3, 3] }, + down: { uv: [2, 2, 3, 3] }, + }, - {"size": [10,1,1], "pos": [8, 13.5, -0.5], "origin": [0, 0, 0], "north":{"uv":[3,2,13,3]},"east":{"uv":[3,2,13,3]},"south":{"uv":[3,2,13,3]},"west":{"uv":[3,2,13,3]},"up":{"uv":[3,2,13,3]},"down":{"uv":[3,2,13,3]}}, - {"size": [10,1,1], "pos": [8, 2.5, -0.5], "origin": [0, 0, 0], "north":{"uv":[3,13,13,14]},"east":{"uv":[3,13,13,14]},"south":{"uv":[3,13,13,14]},"west":{"uv":[3,13,13,14]},"up":{"uv":[3,13,13,14]},"down":{"uv":[3,13,13,14]}} + { + size: [10, 1, 1], + pos: [8, 13.5, -0.5], + origin: [0, 0, 0], + north: { uv: [3, 2, 13, 3] }, + east: { uv: [3, 2, 13, 3] }, + south: { uv: [3, 2, 13, 3] }, + west: { uv: [3, 2, 13, 3] }, + up: { uv: [3, 2, 13, 3] }, + down: { uv: [3, 2, 13, 3] }, + }, + { + size: [10, 1, 1], + pos: [8, 2.5, -0.5], + origin: [0, 0, 0], + north: { uv: [3, 13, 13, 14] }, + east: { uv: [3, 13, 13, 14] }, + south: { uv: [3, 13, 13, 14] }, + west: { uv: [3, 13, 13, 14] }, + up: { uv: [3, 13, 13, 14] }, + down: { uv: [3, 13, 13, 14] }, + }, ], }, frame_top_block: { texture: 'assets/missing.png', cubes: [ - {"size": [16,16,16], "pos": [8, -8.01, 8], "origin": [0, 0, 0], "north":{"uv":[0,0,16,16]},"east":{"uv":[0,0,16,16]},"south":{"uv":[0,0,16,16]},"west":{"uv":[0,0,16,16]},"up":{"uv":[0,0,16,16]},"down":{"uv":[0,0,16,16]}} - ] + { + size: [16, 16, 16], + pos: [8, -8.01, 8], + origin: [0, 0, 0], + north: { uv: [0, 0, 16, 16] }, + east: { uv: [0, 0, 16, 16] }, + south: { uv: [0, 0, 16, 16] }, + west: { uv: [0, 0, 16, 16] }, + up: { uv: [0, 0, 16, 16] }, + down: { uv: [0, 0, 16, 16] }, + }, + ], }, frame_top: { texture: 'assets/item_frame.png', elements: [ - {"rotation": [90, 0, 0], "size": [10,10,0.5], "pos": [8, -8, -0.25], "origin": [8, -8, 8], "north":{"uv":[3,3,13,13]},"east":{"uv":[0,0,0,0]},"south":{"uv":[0,0,0,0]},"west":{"uv":[0,0,0,0]},"up":{"uv":[0,0,0,0]},"down":{"uv":[0,0,0,0]}}, + { + rotation: [90, 0, 0], + size: [10, 10, 0.5], + pos: [8, -8, -0.25], + origin: [8, -8, 8], + north: { uv: [3, 3, 13, 13] }, + east: { uv: [0, 0, 0, 0] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [0, 0, 0, 0] }, + up: { uv: [0, 0, 0, 0] }, + down: { uv: [0, 0, 0, 0] }, + }, - {"rotation": [90, 0, 0], "size": [1,12,1], "pos": [13.5, -8, -0.5], "origin": [8, -8, 8], "north":{"uv":[2,2,3,14]},"east":{"uv":[2,2,3,14]},"south":{"uv":[2,2,3,14]},"west":{"uv":[2,2,3,14]},"up":{"uv":[2,2,3,3]},"down":{"uv":[2,2,3,3]}}, - {"rotation": [90, 0, 0], "size": [1,12,1], "pos": [2.5, -8, -0.5], "origin": [8, -8, 8], "north":{"uv":[2,2,3,14]},"east":{"uv":[2,2,3,14]},"south":{"uv":[2,2,3,14]},"west":{"uv":[2,2,3,14]},"up":{"uv":[2,2,3,3]},"down":{"uv":[2,2,3,3]}}, + { + rotation: [90, 0, 0], + size: [1, 12, 1], + pos: [13.5, -8, -0.5], + origin: [8, -8, 8], + north: { uv: [2, 2, 3, 14] }, + east: { uv: [2, 2, 3, 14] }, + south: { uv: [2, 2, 3, 14] }, + west: { uv: [2, 2, 3, 14] }, + up: { uv: [2, 2, 3, 3] }, + down: { uv: [2, 2, 3, 3] }, + }, + { + rotation: [90, 0, 0], + size: [1, 12, 1], + pos: [2.5, -8, -0.5], + origin: [8, -8, 8], + north: { uv: [2, 2, 3, 14] }, + east: { uv: [2, 2, 3, 14] }, + south: { uv: [2, 2, 3, 14] }, + west: { uv: [2, 2, 3, 14] }, + up: { uv: [2, 2, 3, 3] }, + down: { uv: [2, 2, 3, 3] }, + }, - {"rotation": [90, 0, 0], "size": [10,1,1], "pos": [8, -2.5, -0.5], "origin": [8, -8, 8], "north":{"uv":[3,2,13,3]},"east":{"uv":[3,2,13,3]},"south":{"uv":[3,2,13,3]},"west":{"uv":[3,2,13,3]},"up":{"uv":[3,2,13,3]},"down":{"uv":[3,2,13,3]}}, - {"rotation": [90, 0, 0], "size": [10,1,1], "pos": [8, -13.5, -0.5], "origin": [8, -8, 8], "north":{"uv":[3,13,13,14]},"east":{"uv":[3,13,13,14]},"south":{"uv":[3,13,13,14]},"west":{"uv":[3,13,13,14]},"up":{"uv":[3,13,13,14]},"down":{"uv":[3,13,13,14]}} - ] + { + rotation: [90, 0, 0], + size: [10, 1, 1], + pos: [8, -2.5, -0.5], + origin: [8, -8, 8], + north: { uv: [3, 2, 13, 3] }, + east: { uv: [3, 2, 13, 3] }, + south: { uv: [3, 2, 13, 3] }, + west: { uv: [3, 2, 13, 3] }, + up: { uv: [3, 2, 13, 3] }, + down: { uv: [3, 2, 13, 3] }, + }, + { + rotation: [90, 0, 0], + size: [10, 1, 1], + pos: [8, -13.5, -0.5], + origin: [8, -8, 8], + north: { uv: [3, 13, 13, 14] }, + east: { uv: [3, 13, 13, 14] }, + south: { uv: [3, 13, 13, 14] }, + west: { uv: [3, 13, 13, 14] }, + up: { uv: [3, 13, 13, 14] }, + down: { uv: [3, 13, 13, 14] }, + }, + ], }, shelf: { texture: 'assets/oak_shelf.png', elements: [ - {"size": [16,16,3], "pos": [8, 8, 14.5], "origin": [0, 0, 0], "north":{"uv":[0,0,8,8]},"east":{"uv":[8,0,9.5,8]},"south":{"uv":[8,0,16,8]},"west":{"uv":[14.5,0,16,8]},"up":{"uv":[16,5,8,3.5]},"down":{"uv":[16,6,8,4.5]}}, - {"size": [16,4,2], "pos": [8, 2, 12], "origin": [0, 0, 0], "north":{"uv":[0,6,8,8]},"east":{"uv":[1.5,6,2.5,8]},"south":{"uv":[0,0,0,0]},"west":{"uv":[5.5,6,6.5,8]},"up":{"uv":[8,3.5,16,4.5]},"down":{"uv":[16,4.5,8,3.5]}}, - {"size": [16,4,2], "pos": [8, 14, 12], "origin": [0, 0, 0], "north":{"uv":[0,0,8,2]},"east":{"uv":[1.5,0,2.5,2]},"south":{"uv":[0,0,0,0]},"west":{"uv":[5.5,0,6.5,2]},"up":{"uv":[16,6,8,5]},"down":{"uv":[8,5,16,6]}} - ] - } -}; -export default DisplayReferences; - + { + size: [16, 16, 3], + pos: [8, 8, 14.5], + origin: [0, 0, 0], + north: { uv: [0, 0, 8, 8] }, + east: { uv: [8, 0, 9.5, 8] }, + south: { uv: [8, 0, 16, 8] }, + west: { uv: [14.5, 0, 16, 8] }, + up: { uv: [16, 5, 8, 3.5] }, + down: { uv: [16, 6, 8, 4.5] }, + }, + { + size: [16, 4, 2], + pos: [8, 2, 12], + origin: [0, 0, 0], + north: { uv: [0, 6, 8, 8] }, + east: { uv: [1.5, 6, 2.5, 8] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [5.5, 6, 6.5, 8] }, + up: { uv: [8, 3.5, 16, 4.5] }, + down: { uv: [16, 4.5, 8, 3.5] }, + }, + { + size: [16, 4, 2], + pos: [8, 14, 12], + origin: [0, 0, 0], + north: { uv: [0, 0, 8, 2] }, + east: { uv: [1.5, 0, 2.5, 2] }, + south: { uv: [0, 0, 0, 0] }, + west: { uv: [5.5, 0, 6.5, 2] }, + up: { uv: [16, 6, 8, 5] }, + down: { uv: [8, 5, 16, 6] }, + }, + ], + }, +} +export default DisplayReferences diff --git a/js/file_system.ts b/js/file_system.ts index 00e7d1ddf..4e62882ac 100644 --- a/js/file_system.ts +++ b/js/file_system.ts @@ -1,11 +1,11 @@ import saveAs from 'file-saver' import StateMemory from './util/state_memory' -import { pathToExtension } from './util/util'; -import { app, currentwindow, electron, fs, ipcRenderer, webUtils } from './native_apis'; +import { pathToExtension } from './util/util' +import { app, currentwindow, electron, fs, ipcRenderer, webUtils } from './native_apis' function isStreamerMode(): boolean { // @ts-ignore - return window.settings.streamer_mode.value; + return window.settings.streamer_mode.value } declare class Blockbench { @@ -76,11 +76,11 @@ export namespace Filesystem { * Opens a file picker dialog to import one or multiple files into Blockbench * @param options Import options * @param callback Callback to run once the files are imported - * @returns + * @returns */ export function importFile(options: ImportOptions, callback?: (files: FileResult[]) => void) { if (isApp) { - let properties = []; + let properties = [] if (options.multiple) { properties.push('openFile', 'multiSelections') } @@ -92,92 +92,107 @@ export namespace Filesystem { options.startpath = StateMemory.get('dialog_paths')[options.resource_id] } - let fileNames = electron.dialog.showOpenDialogSync( - currentwindow, - { - title: options.title ? options.title : '', - filters: [{ + let fileNames = electron.dialog.showOpenDialogSync(currentwindow, { + title: options.title ? options.title : '', + filters: [ + { name: options.type ? options.type : options.extensions[0], - extensions: options.extensions - }], - properties: properties.length ? properties.concat(['dontAddToRecent']) : ['dontAddToRecent'], - defaultPath: isStreamerMode() - ? app.getPath('desktop') - : options.startpath - } - ) - if (!fileNames) return; + extensions: options.extensions, + }, + ], + properties: properties.length + ? properties.concat(['dontAddToRecent']) + : ['dontAddToRecent'], + defaultPath: isStreamerMode() ? app.getPath('desktop') : options.startpath, + }) + if (!fileNames) return if (options.resource_id) { - StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname(fileNames[0]) + StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname( + fileNames[0] + ) StateMemory.save('dialog_paths') } readFile(fileNames, options, callback) } else { - let isIOS = ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) || - (navigator.userAgent.includes("Mac") && "ontouchend" in document); + let isIOS = + [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod', + ].includes(navigator.platform) || + (navigator.userAgent.includes('Mac') && 'ontouchend' in document) let element = Interface.createElement('input', { type: 'file', - accept: '.'+(options.extensions ? options.extensions.join(',.'): ''), + accept: '.' + (options.extensions ? options.extensions.join(',.') : ''), multiple: options.multiple === true ? 'true' : 'false', '@change'(event: Event) { - readFile(this.files, options, callback); - - } - }) as HTMLInputElement; - - if ((isIOS || Blockbench.isTouch) && options.extensions && options.extensions.length > 1) { - let ext_options = {}; + readFile(this.files, options, callback) + }, + }) as HTMLInputElement + + if ( + (isIOS || Blockbench.isTouch) && + options.extensions && + options.extensions.length > 1 + ) { + let ext_options = {} options.extensions.forEach(extension => { - ext_options[extension] = extension; + ext_options[extension] = extension }) new Dialog({ id: 'import_type', title: 'File Type', form: { - extension: {label: 'File Type', type: 'select', options: ext_options} + extension: { label: 'File Type', type: 'select', options: ext_options }, }, onConfirm(formResult) { - element.setAttribute('accept', '.'+formResult.extension); - $(element).trigger('click'); - } - }).show(); + element.setAttribute('accept', '.' + formResult.extension) + $(element).trigger('click') + }, + }).show() } else { - $(element).trigger('click'); + $(element).trigger('click') } } } - // MARK: Read - export function readFile(files: string[] | FileList, options: ReadOptions = {}, callback?: (files: FileResult[]) => void) { - if (files == undefined) return false; - if (typeof files == 'string') files = [files]; + export function readFile( + files: string[] | FileList, + options: ReadOptions = {}, + callback?: (files: FileResult[]) => void + ) { + if (files == undefined) return false + if (typeof files == 'string') files = [files] - let results: FileResult[] = []; - let result_count = 0; - let errant = false; + let results: FileResult[] = [] + let result_count = 0 + let errant = false if (isApp && files instanceof FileList == false) { if (options.readtype == 'none') { let results = files.map(file => { return { name: pathToName(file, true), - path: file + path: file, } }) - callback(results); - return results; + callback(results) + return results } files.forEach((file, i) => { - let readtype: ReadType; + let readtype: ReadType if (typeof options.readtype == 'function') { - readtype = options.readtype(file); + readtype = options.readtype(file) } else { readtype = options.readtype } - let binary = (readtype === 'buffer' || readtype === 'binary'); + let binary = readtype === 'buffer' || readtype === 'binary' if (!readtype) { - readtype = 'text'; + readtype = 'text' } if (readtype === 'image') { @@ -186,77 +201,83 @@ export namespace Filesystem { if (extension === 'tga') { let targa_loader = new Targa() targa_loader.open(file, () => { - results[i] = { name: pathToName(file, true), path: file, - content: targa_loader.getDataURL() + content: targa_loader.getDataURL(), } - - result_count++; + + result_count++ if (result_count === files.length) { callback(results) } }) - } else { results[i] = { name: pathToName(file, true), path: file, - content: file + content: file, } - result_count++; + result_count++ if (result_count === files.length) { callback(results) } } - } else /*text*/ { - let data; + } /*text*/ else { + let data try { - data = fs.readFileSync(file, readtype == 'text' ? 'utf8' : undefined); - } catch(err) { + data = fs.readFileSync(file, readtype == 'text' ? 'utf8' : undefined) + } catch (err) { console.error(err) if (!errant && options.errorbox !== false) { Blockbench.showMessageBox({ translateKey: 'file_not_found', - message: tl('message.file_not_found.message') + '\n\n```' + file.replace(/[`"<>]/g, '') + '```', + message: + tl('message.file_not_found.message') + + '\n\n```' + + file.replace(/[`"<>]/g, '') + + '```', icon: 'error_outline', - width: 520 + width: 520, }) } - errant = true; - return; + errant = true + return } if (binary) { - let ab = new ArrayBuffer(data.length); - let view = new Uint8Array(ab); + let ab = new ArrayBuffer(data.length) + let view = new Uint8Array(ab) for (let i = 0; i < data.length; ++i) { - view[i] = data[i]; + view[i] = data[i] } - data = ab; + data = ab } - if (!binary && data.charCodeAt(0) === 0xFEFF) { + if (!binary && data.charCodeAt(0) === 0xfeff) { data = data.substr(1) } results[i] = { name: pathToName(file, true), path: file, - content: data + content: data, } - result_count++; + result_count++ if (result_count === files.length) { callback(results) } } - }); + }) } else { - let i = 0; - for (let file of (files as FileList)) { + let i = 0 + for (let file of files as FileList) { let reader = new FileReader() - let local_i = i; - reader.onloadend = function() { - let result; - if (typeof reader.result != 'string' && reader.result.byteLength && pathToExtension(name) === 'tga') { + let local_i = i + reader.onloadend = function () { + let result + if ( + typeof reader.result != 'string' && + reader.result.byteLength && + pathToExtension(name) === 'tga' + ) { let arr = new Uint8Array(reader.result) let targa_loader = new Targa() targa_loader.load(arr) @@ -268,20 +289,27 @@ export namespace Filesystem { name, path: name, content: result, - browser_file: file + browser_file: file, } - result_count++; + result_count++ if (result_count === files.length) { callback(results) } } - let name = file.name; - if (pathToExtension(name) === 'txt' && !(options && options.extensions instanceof Array && options.extensions.includes('txt'))) { - name = name.replace(/\.txt$/i, ''); + let name = file.name + if ( + pathToExtension(name) === 'txt' && + !( + options && + options.extensions instanceof Array && + options.extensions.includes('txt') + ) + ) { + name = name.replace(/\.txt$/i, '') } - let readtype = options.readtype; + let readtype = options.readtype if (typeof readtype == 'function') { - readtype = readtype(name); + readtype = readtype(name) } if (readtype === 'image') { if (pathToExtension(name) === 'tga') { @@ -291,18 +319,15 @@ export namespace Filesystem { } } else if (readtype === 'buffer' || readtype === 'binary') { reader.readAsArrayBuffer(file) - } else /*text*/ { + } /*text*/ else { reader.readAsText(file) } - i++; + i++ } } } - export const read = readFile; + export const read = readFile - - - // MARK: Pick Directory interface PickDirOptions { /**Location where the file dialog starts off @@ -320,7 +345,6 @@ export namespace Filesystem { */ export function pickDirectory(options: PickDirOptions = {}): string | undefined { if (isApp) { - if (!options.startpath && options.resource_id) { options.startpath = StateMemory.get('dialog_paths')[options.resource_id] } @@ -328,24 +352,21 @@ export namespace Filesystem { let dirNames = electron.dialog.showOpenDialogSync(currentwindow, { title: options.title ? options.title : '', properties: ['openDirectory', 'dontAddToRecent'], - defaultPath: isStreamerMode() - ? app.getPath('desktop') - : options.startpath + defaultPath: isStreamerMode() ? app.getPath('desktop') : options.startpath, }) - if (!dirNames) return null; + if (!dirNames) return null if (options.resource_id) { - StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname(dirNames[0]); - StateMemory.save('dialog_paths'); + StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname( + dirNames[0] + ) + StateMemory.save('dialog_paths') } - return dirNames[0]; - + return dirNames[0] } else { - - console.warn('Picking directories is currently not supported in the web app'); - + console.warn('Picking directories is currently not supported in the web app') } } @@ -376,7 +397,7 @@ export namespace Filesystem { * Open a file save dialog to let the user pick a location and name to save a file. On the web app, this might save the file directoy into the downloads folder depending on browser settings. * @param options Export options * @param callback Callback to run once the file is saved - * @returns + * @returns */ export function exportFile(options: ExportOptions, callback?: (file_path: string) => void) { /* @@ -391,36 +412,40 @@ export namespace Filesystem { resource_id */ if (!isApp) { - let file_name = options.name || 'file'; - let extension = pathToExtension(file_name); - if (options.extensions instanceof Array && !options.extensions.includes(extension) && options.extensions[0]) { - file_name += '.' + options.extensions[0]; + let file_name = options.name || 'file' + let extension = pathToExtension(file_name) + if ( + options.extensions instanceof Array && + !options.extensions.includes(extension) && + options.extensions[0] + ) { + file_name += '.' + options.extensions[0] } if (options.custom_writer) { options.custom_writer(options.content, file_name) - } else { - if (options.savetype === 'image') { saveAs(options.content, file_name, {}) - - } else if (options.savetype === 'zip' || options.savetype === 'buffer' || options.savetype === 'binary') { - let blob = options.content instanceof Blob - ? options.content - : new Blob([options.content], {type: "octet/stream"}); + } else if ( + options.savetype === 'zip' || + options.savetype === 'buffer' || + options.savetype === 'binary' + ) { + let blob = + options.content instanceof Blob + ? options.content + : new Blob([options.content], { type: 'octet/stream' }) saveAs(blob, file_name) - } else { - let type = 'text/plain;charset=utf-8'; + let type = 'text/plain;charset=utf-8' if (file_name.endsWith('json')) { - type = 'application/json;charset=utf-8'; + type = 'application/json;charset=utf-8' } else if (file_name.endsWith('bbmodel')) { - type = 'model/vnd.blockbench.bbmodel'; + type = 'model/vnd.blockbench.bbmodel' } - let blob = new Blob([options.content], {type}); - saveAs(blob, file_name, {autoBOM: true}) + let blob = new Blob([options.content], { type }) + saveAs(blob, file_name, { autoBOM: true }) } - } if (typeof callback === 'function') { callback(file_name) @@ -429,41 +454,53 @@ export namespace Filesystem { if (!options.startpath && options.resource_id) { options.startpath = StateMemory.get('dialog_paths')[options.resource_id] if (options.name) { - options.startpath += osfs + options.name + (options.extensions ? '.'+options.extensions[0] : ''); + options.startpath += + osfs + + options.name + + (options.extensions ? '.' + options.extensions[0] : '') } } let file_path = electron.dialog.showSaveDialogSync(currentwindow, { - filters: [ { - name: options.type, - extensions: options.extensions - } ], + filters: [ + { + name: options.type, + extensions: options.extensions, + }, + ], properties: ['dontAddToRecent'], defaultPath: isStreamerMode() ? app.getPath('desktop') - : ((options.startpath && options.startpath !== 'Unknown') + : options.startpath && options.startpath !== 'Unknown' ? options.startpath.replace(/\.\w+$/, '') - : options.name) + : options.name, }) - if (!file_path) return; + if (!file_path) return if (options.resource_id) { StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname(file_path) StateMemory.save('dialog_paths') } - let extension = pathToExtension(file_path); - if (options.extensions instanceof Array && !options.extensions.includes(extension) && options.extensions[0]) { - file_path += '.'+options.extensions[0] + let extension = pathToExtension(file_path) + if ( + options.extensions instanceof Array && + !options.extensions.includes(extension) && + options.extensions[0] + ) { + file_path += '.' + options.extensions[0] } writeFile(file_path, options, callback) } } - // MARK: Write type WriteType = 'text' | 'buffer' | 'binary' | 'zip' | 'image' interface WriteOptions { content?: string | ArrayBuffer | Blob savetype?: WriteType | ((file: string) => WriteType) - custom_writer?: (content: string | ArrayBuffer | Blob, file_path: string, callback?: (file_path: string) => void) => void + custom_writer?: ( + content: string | ArrayBuffer | Blob, + file_path: string, + callback?: (file_path: string) => void + ) => void } /** * Writes a file to the file system. Desktop app only. @@ -474,40 +511,38 @@ export namespace Filesystem { callback?: (file_path: string) => void ) { if (!isApp || !file_path) { - return; + return } if (options.savetype === 'image' && typeof options.content === 'string') { if (options.content.substr(0, 10) === 'data:image') { - fs.writeFileSync(file_path, options.content.split(',')[1], {encoding: 'base64'}) + fs.writeFileSync(file_path, options.content.split(',')[1], { encoding: 'base64' }) if (callback) callback(file_path) } else { - let path = options.content.replace(/\?\d+$/, ''); + let path = options.content.replace(/\?\d+$/, '') if (PathModule.relative(path, file_path)) { - fs.copyFileSync(path, file_path); + fs.copyFileSync(path, file_path) } if (callback) callback(file_path) } - return; + return } if (options.custom_writer) { options.custom_writer(options.content, file_path, callback) - } else if (options.savetype === 'zip') { - let fileReader = new FileReader(); - fileReader.onload = function(event) { - let buffer = Buffer.from(new Uint8Array(this.result as ArrayBuffer)); + let fileReader = new FileReader() + fileReader.onload = function (event) { + let buffer = Buffer.from(new Uint8Array(this.result as ArrayBuffer)) fs.writeFileSync(file_path, buffer) if (callback) { callback(file_path) } - }; - fileReader.readAsArrayBuffer(options.content as Blob); - + } + fileReader.readAsArrayBuffer(options.content as Blob) } else { //text or binary - let content = options.content; + let content = options.content if (content instanceof ArrayBuffer) { - content = Buffer.from(content); + content = Buffer.from(content) } fs.writeFileSync(file_path, content as string) if (callback) { @@ -516,14 +551,11 @@ export namespace Filesystem { } } - // MARK: Open export function showFileInFolder(path: string) { - ipcRenderer.send('show-item-in-folder', path); + ipcRenderer.send('show-item-in-folder', path) } - - // MARK: Find interface FindFileOptions { recursive: boolean @@ -539,65 +571,63 @@ export namespace Filesystem { export function findFileFromContent( base_directories: string[], options: FindFileOptions, - check_file: (path: string, content: string|object) => boolean + check_file: (path: string, content: string | object) => boolean ) { - let deprioritized_files = []; + let deprioritized_files = [] function checkFile(path) { try { - let content: string; - if (options.read_file !== false) content = fs.readFileSync(path, 'utf-8'); - - return check_file(path, options.json ? autoParseJSON(content, false) : content); + let content: string + if (options.read_file !== false) content = fs.readFileSync(path, 'utf-8') + return check_file(path, options.json ? autoParseJSON(content, false) : content) } catch (err) { - console.error(err); - return false; + console.error(err) + return false } } let searchFolder = (path: string) => { - let files; + let files try { - files = fs.readdirSync(path, {withFileTypes: true}); + files = fs.readdirSync(path, { withFileTypes: true }) } catch (err) { - files = []; + files = [] } for (let dirent of files) { - if (dirent.isDirectory()) continue; + if (dirent.isDirectory()) continue if (!options.filter_regex || options.filter_regex.exec(dirent.name)) { - let new_path = path + osfs + dirent.name; + let new_path = path + osfs + dirent.name if (!options.priority_regex || options.priority_regex.exec(dirent.name)) { // priority checking - let result = checkFile(new_path); - if (result) return result; + let result = checkFile(new_path) + if (result) return result } else { - deprioritized_files.push(new_path); + deprioritized_files.push(new_path) } } } if (options.recursive !== false) { for (let dirent of files) { - if (!dirent.isDirectory()) continue; + if (!dirent.isDirectory()) continue - let result = searchFolder(path + osfs + dirent.name); - if (result) return result; + let result = searchFolder(path + osfs + dirent.name) + if (result) return result } } } for (let directory of base_directories) { - let result = searchFolder(directory); - if (result) return result; + let result = searchFolder(directory) + if (result) return result } for (let path of deprioritized_files) { - let result = checkFile(path); - if (result) return result; + let result = checkFile(path) + if (result) return result } } - // MARK: Drag & Drop interface DragHandlerOptions { /** @@ -621,60 +651,64 @@ export namespace Filesystem { * Whether to display an error box when importing a dragged file fails */ errorbox?: boolean - }; + } export interface DragHandler extends DragHandlerOptions { cb: (files: FileResult[], event: DragEvent) => void delete: () => void - }; - export const drag_handlers: Record = {}; + } + export const drag_handlers: Record = {} /** * Handle files being drag & dropped into Blockbench * @param id ID of the handler * @param options Options * @param callback Callback when a file is dropped - * @returns + * @returns */ - export function addDragHandler(id: string, options: DragHandlerOptions, callback?: (files: FileResult[], event: DragEvent) => void): DragHandler { + export function addDragHandler( + id: string, + options: DragHandlerOptions, + callback?: (files: FileResult[], event: DragEvent) => void + ): DragHandler { let entry: DragHandler = { cb: callback, condition: options.condition, extensions: options.extensions, delete() { - Filesystem.removeDragHandler(id); - } + Filesystem.removeDragHandler(id) + }, } - if (options.propagate) entry.propagate = true; - if (options.readtype) entry.readtype = options.readtype; - if (options.errorbox) entry.errorbox = true; - if (options.element) entry.element = options.element; + if (options.propagate) entry.propagate = true + if (options.readtype) entry.readtype = options.readtype + if (options.errorbox) entry.errorbox = true + if (options.element) entry.element = options.element - drag_handlers[id] = entry; - return entry; + drag_handlers[id] = entry + return entry } export function removeDragHandler(id: string) { - delete drag_handlers[id]; + delete drag_handlers[id] } - document.ondragover = function(event) { + document.ondragover = function (event) { event.preventDefault() } - document.body.ondrop = function(event) { + document.body.ondrop = function (event) { event.preventDefault() - let text = event.dataTransfer.getData('text/plain'); + let text = event.dataTransfer.getData('text/plain') if (text && text.startsWith('https://blckbn.ch/')) { - let code = text.replace(/\/$/, '').split('/').last(); - $.getJSON(`https://blckbn.ch/api/models/${code}`, (model) => { - Codecs.project.load(model, {path: ''}); + let code = text.replace(/\/$/, '').split('/').last() + $.getJSON(`https://blckbn.ch/api/models/${code}`, model => { + Codecs.project.load(model, { path: '' }) }).fail(error => { Blockbench.showQuickMessage('message.invalid_link') }) } - forDragHandlers(event, function(handler, el) { + forDragHandlers(event, function (handler, el) { let fileNames = event.dataTransfer.files - let paths: string[] | FileList = []; + let paths: string[] | FileList = [] if (isApp) { for (let file of fileNames) { if ('path' in file) { @@ -682,84 +716,94 @@ export namespace Filesystem { paths.push(file.path) } else if (isApp) { // @ts-ignore - let path = webUtils.getPathForFile(file); - paths.push(path); + let path = webUtils.getPathForFile(file) + paths.push(path) } } } else { - paths = fileNames; + paths = fileNames } - if (!paths.length) return; + if (!paths.length) return let read_options = { - extensions: (typeof handler.extensions == 'function' ? handler.extensions : handler.extensions) as string[], + extensions: (typeof handler.extensions == 'function' + ? handler.extensions + : handler.extensions) as string[], readtype: handler.readtype, errorbox: handler.errorbox, } - Filesystem.read(paths, read_options, (files) => { + Filesystem.read(paths, read_options, files => { handler.cb(files, event) }) }) } - document.body.ondragenter = function(event) { + document.body.ondragenter = function (event) { event.preventDefault() - forDragHandlers(event, function(handler, el) { + forDragHandlers(event, function (handler, el) { //$(el).css('background-color', 'red') }) } - document.body.ondragleave = function(event) { + document.body.ondragleave = function (event) { event.preventDefault() - forDragHandlers(event, function(handler, el) { + forDragHandlers(event, function (handler, el) { //$(el).css('background-color', '') }) } - function forDragHandlers(event: DragEvent, cb: (handler: Filesystem.DragHandler, el: HTMLElement) => void) { - if (event.dataTransfer == undefined || event.dataTransfer.files.length == 0 || !event.dataTransfer.files[0].name) { - return; + function forDragHandlers( + event: DragEvent, + cb: (handler: Filesystem.DragHandler, el: HTMLElement) => void + ) { + if ( + event.dataTransfer == undefined || + event.dataTransfer.files.length == 0 || + !event.dataTransfer.files[0].name + ) { + return } for (let id in Filesystem.drag_handlers) { - let handler = Filesystem.drag_handlers[id] - let el = undefined; + let handler = Filesystem.drag_handlers[id] + let el = undefined if (!Condition(handler.condition)) { - continue; + continue } if (!handler.element) { - el = document.body; - + el = document.body } else if (typeof handler.element === 'function') { let result = handler.element() if (result === true) { el = $(event.target) } else if ($(result as HTMLElement).length) { - el = $(result as HTMLElement).get(0); + el = $(result as HTMLElement).get(0) } - } else if ($(handler.element as HTMLElement).get(0) === event.target) { el = event.target - } else if (typeof handler.element === 'string' && $(event.target).is(handler.element)) { el = event.target } else if (handler.propagate) { let parent = $(handler.element as HTMLElement) if (parent && parent.has(event.target as HTMLElement).length) { - el = parent; + el = parent } } - let extensions = typeof handler.extensions == 'function' ? handler.extensions() : handler.extensions; - extensions.includes( pathToExtension(event.dataTransfer.files[0].name).toLowerCase()); - let name = event.dataTransfer.files[0].name; - if (el && extensions.filter(ex => { - return name.substr(-ex.length) == ex; - }).length) { + let extensions = + typeof handler.extensions == 'function' ? handler.extensions() : handler.extensions + extensions.includes(pathToExtension(event.dataTransfer.files[0].name).toLowerCase()) + let name = event.dataTransfer.files[0].name + if ( + el && + extensions.filter(ex => { + return name.substr(-ex.length) == ex + }).length + ) { cb(handler, el) - break; + break } } } } Object.assign(window, { - Filesystem + Filesystem, }) diff --git a/js/global_types.ts b/js/global_types.ts index cd3e5c727..f7bc4c875 100644 --- a/js/global_types.ts +++ b/js/global_types.ts @@ -1,4 +1,4 @@ -import { ModelFormat as _ModelFormat } from "./io/format" +import { ModelFormat as _ModelFormat } from './io/format' declare global { const ModelFormat: typeof _ModelFormat const Format: _ModelFormat @@ -26,10 +26,7 @@ declare global { const Settings: typeof _Settings } } -import { - Modes as _Modes, - Mode as _Mode, -} from './modes' +import { Modes as _Modes, Mode as _Mode } from './modes' declare global { const Modes: typeof _Modes const Mode: typeof _Mode @@ -50,9 +47,7 @@ declare global { const ShapelessDialog: typeof _ShapelessDialog const ToolConfig: typeof _ToolConfig } -import { - Property as _Property, -} from './util/property' +import { Property as _Property } from './util/property' declare global { const Property: typeof _Property } diff --git a/js/interface/dialog.ts b/js/interface/dialog.ts index 99fbb21a9..2b8cfa0d2 100644 --- a/js/interface/dialog.ts +++ b/js/interface/dialog.ts @@ -1,8 +1,8 @@ -import { Blockbench } from "../api" -import { Prop } from "../misc" -import { FormElementOptions, FormResultValue, InputForm, InputFormConfig } from "./form" +import { Blockbench } from '../api' +import { Prop } from '../misc' +import { FormElementOptions, FormResultValue, InputForm, InputFormConfig } from './form' import { Vue } from './../lib/libs' -import { getStringWidth } from "../util/util" +import { getStringWidth } from '../util/util' interface ActionInterface { name: string @@ -12,7 +12,7 @@ interface ActionInterface { click(event: Event): void condition?: ConditionResolvable } -type DialogLineOptions = ( +type DialogLineOptions = | HTMLElement | { label?: string @@ -21,33 +21,38 @@ type DialogLineOptions = ( node?: HTMLElement } | string -) function buildForm(dialog: Dialog) { - dialog.form = new InputForm(dialog.form_config); - let dialog_content = $(dialog.object).find('.dialog_content'); - dialog_content.append(dialog.form.node); - dialog.max_label_width = Math.max(dialog.max_label_width, dialog.form.max_label_width); - if (dialog.form.uses_wide_inputs) dialog.uses_wide_inputs = true; - dialog.form.on('change', ({result}) => { - if (dialog.onFormChange) dialog.onFormChange(result); + dialog.form = new InputForm(dialog.form_config) + let dialog_content = $(dialog.object).find('.dialog_content') + dialog_content.append(dialog.form.node) + dialog.max_label_width = Math.max(dialog.max_label_width, dialog.form.max_label_width) + if (dialog.form.uses_wide_inputs) dialog.uses_wide_inputs = true + dialog.form.on('change', ({ result }) => { + if (dialog.onFormChange) dialog.onFormChange(result) }) } function buildLines(dialog: Dialog) { - let dialog_content = dialog.object.querySelector('.dialog_content'); + let dialog_content = dialog.object.querySelector('.dialog_content') dialog.lines.forEach(l => { if (typeof l === 'object' && ('label' in l || 'widget' in l)) { - - let bar = Interface.createElement('div', {class: 'dialog_bar'}); + let bar = Interface.createElement('div', { class: 'dialog_bar' }) if (l.label) { - let label = Interface.createElement('label', {class: 'name_space_left'}, tl(l.label)) - bar.append(label); - dialog.max_label_width = Math.max(getStringWidth(label.textContent), dialog.max_label_width) + let label = Interface.createElement( + 'label', + { class: 'name_space_left' }, + tl(l.label) + ) + bar.append(label) + dialog.max_label_width = Math.max( + getStringWidth(label.textContent), + dialog.max_label_width + ) } if (l.node) { bar.append(l.node) } else if (l.widget) { - let widget: Widget; + let widget: Widget if (typeof l.widget === 'string') { widget = BarItems[l.widget] } else if (typeof l.widget === 'function') { @@ -55,40 +60,43 @@ function buildLines(dialog: Dialog) { } else { widget = l.widget } - bar.append(widget.getNode()); - dialog.max_label_width = Math.max(getStringWidth(widget.name), dialog.max_label_width) + bar.append(widget.getNode()) + dialog.max_label_width = Math.max( + getStringWidth(widget.name), + dialog.max_label_width + ) } - dialog.uses_wide_inputs = true; - dialog_content.append(bar); + dialog.uses_wide_inputs = true + dialog_content.append(bar) } else if (typeof l == 'string') { - dialog_content.append(document.createTextNode(l)); + dialog_content.append(document.createTextNode(l)) } else if (l instanceof HTMLElement) { - dialog_content.append(l); + dialog_content.append(l) } }) } function buildComponent(dialog: Dialog) { - let dialog_content = $(dialog.object).find('.dialog_content').get(0); - let mount: HTMLElement; + let dialog_content = $(dialog.object).find('.dialog_content').get(0) + let mount: HTMLElement // mount_directly, if enabled, skips one layer of wrapper. Class "dialog_content" must be added the the root element of the vue component. if (dialog.component.mount_directly) { - mount = dialog_content; + mount = dialog_content } else { - mount = Interface.createElement('div'); - dialog_content.append(mount); + mount = Interface.createElement('div') + dialog_content.append(mount) } dialog.component.name = 'dialog-content' - dialog.content_vue = new Vue(dialog.component).$mount(mount); + dialog.content_vue = new Vue(dialog.component).$mount(mount) } function buildToolbars(dialog: Dialog) { let dialog_content = $(dialog.object).find('.dialog_content') for (let id in dialog.toolbars) { - let toolbar = dialog.toolbars[id]; - dialog_content.append(toolbar.node); + let toolbar = dialog.toolbars[id] + dialog_content.append(toolbar.node) } } -const toggle_sidebar = window.innerWidth < 640; +const toggle_sidebar = window.innerWidth < 640 interface DialogSidebarOptions { pages?: { [key: string]: string | { label: string; icon: IconString; color?: string } | MenuSeparator @@ -111,99 +119,102 @@ export class DialogSidebar { onPageSwitch?(page: string): void constructor(options: DialogSidebarOptions, dialog: Dialog) { - this.open = !toggle_sidebar; - this.pages = options.pages || {}; - this.page = options.page || Object.keys(this.pages)[0]; - this.actions = options.actions || []; - this.dialog = dialog; - this.onPageSwitch = options.onPageSwitch || null; + this.open = !toggle_sidebar + this.pages = options.pages || {} + this.page = options.page || Object.keys(this.pages)[0] + this.actions = options.actions || [] + this.dialog = dialog + this.onPageSwitch = options.onPageSwitch || null } build() { - this.node = document.createElement('div'); - this.node.className = 'dialog_sidebar'; + this.node = document.createElement('div') + this.node.className = 'dialog_sidebar' - let page_list = document.createElement('ul'); - page_list.className = 'dialog_sidebar_pages'; - this.node.append(page_list); - this.page_menu = {}; + let page_list = document.createElement('ul') + page_list.className = 'dialog_sidebar_pages' + this.node.append(page_list) + this.page_menu = {} for (let key in this.pages) { - let page = this.pages[key]; + let page = this.pages[key] if (page instanceof MenuSeparator) { - let expander = Interface.createElement('span'); + let expander = Interface.createElement('span') // @ts-ignore I don't even know what typescript is thinking here - let node = Interface.createElement('div', {class: 'dialog_sidebar_separator'}, page ? [page.label, expander] : expander); - page_list.append(node); - continue; + let node = Interface.createElement( + 'div', + { class: 'dialog_sidebar_separator' }, + page ? [page.label, expander] : expander + ) + page_list.append(node) + continue } - let li = document.createElement('li'); + let li = document.createElement('li') if (typeof page == 'object' && page.icon) { - li.append(Blockbench.getIconNode(page.icon, page.color)); + li.append(Blockbench.getIconNode(page.icon, page.color)) } - li.append(typeof page == 'string' ? tl(page) : tl(page.label)); - li.setAttribute('page', key); - if (this.page == key) li.classList.add('selected'); - this.page_menu[key] = li; + li.append(typeof page == 'string' ? tl(page) : tl(page.label)) + li.setAttribute('page', key) + if (this.page == key) li.classList.add('selected') + this.page_menu[key] = li li.addEventListener('click', event => { - this.setPage(key); - if (toggle_sidebar) this.toggle(); + this.setPage(key) + if (toggle_sidebar) this.toggle() }) - page_list.append(li); + page_list.append(li) } if (this.actions.length) { - let action_list = document.createElement('ul'); - action_list.className = 'dialog_sidebar_actions'; - this.node.append(action_list); + let action_list = document.createElement('ul') + action_list.className = 'dialog_sidebar_actions' + this.node.append(action_list) this.actions.forEach(action => { if (typeof action == 'string') { - action = BarItems[action] as Action; + action = BarItems[action] as Action } - let copy; + let copy if (action instanceof Action) { - copy = action.menu_node.cloneNode(true); + copy = action.menu_node.cloneNode(true) copy.addEventListener('click', event => { - action.trigger(event); + action.trigger(event) }) } else { - copy = document.createElement('li'); - copy.title = action.description ? tl(action.description) : ''; - let icon = Blockbench.getIconNode(action.icon, action.color); - let span = document.createElement('span'); - span.textContent = tl(action.name); - copy.append(icon); - copy.append(span); + copy = document.createElement('li') + copy.title = action.description ? tl(action.description) : '' + let icon = Blockbench.getIconNode(action.icon, action.color) + let span = document.createElement('span') + span.textContent = tl(action.name) + copy.append(icon) + copy.append(span) copy.addEventListener('click', event => { - action.click(event); + action.click(event) }) } - action_list.append(copy); + action_list.append(copy) }) } - this.toggle(this.open); + this.toggle(this.open) - this.dialog.object.querySelector('div.dialog_wrapper').append(this.node); - return this.node; + this.dialog.object.querySelector('div.dialog_wrapper').append(this.node) + return this.node } toggle(state = !this.open) { - this.open = state; + this.open = state if (this.node.parentElement) { - this.node.parentElement.classList.toggle('has_sidebar', this.open); + this.node.parentElement.classList.toggle('has_sidebar', this.open) } } setPage(page: string) { - let allow; - if (this.onPageSwitch) allow = this.onPageSwitch(page); - if (allow === false) return; - this.page = page; + let allow + if (this.onPageSwitch) allow = this.onPageSwitch(page) + if (allow === false) return + this.page = page for (let key in this.page_menu) { - let li = this.page_menu[key]; - li.classList.toggle('selected', key == this.page); + let li = this.page_menu[key] + li.classList.toggle('selected', key == this.page) } } } - interface DialogOptions { title: string id?: string @@ -343,7 +354,6 @@ export class Dialog { confirmIndex: number cancelIndex: number - lines?: DialogLineOptions[] form?: InputForm component?: Vue.Component @@ -383,70 +393,78 @@ export class Dialog { constructor(id: string, options: DialogOptions) constructor(id: string | DialogOptions, options?: DialogOptions) { if (typeof id == 'object') { - options = id; - id = options.id; + options = id + id = options.id } - this.id = id; - this.title = options.title; - - this.lines = options.lines; + this.id = id + this.title = options.title + + this.lines = options.lines this.toolbars = options.toolbars this.form_config = options.form this.component = options.component - this.content_vue = null; - this.part_order = options.part_order || (options.form_first ? ['form', 'lines', 'component'] : ['lines', 'form', 'component']) + this.content_vue = null + this.part_order = + options.part_order || + (options.form_first ? ['form', 'lines', 'component'] : ['lines', 'form', 'component']) - this.sidebar = options.sidebar ? new DialogSidebar(options.sidebar, this) : null; - this.title_menu = options.title_menu || null; + this.sidebar = options.sidebar ? new DialogSidebar(options.sidebar, this) : null + this.title_menu = options.title_menu || null if (options.progress_bar) { this.progress_bar = { setProgress: (progress: number) => { - this.progress_bar.progress = progress; + this.progress_bar.progress = progress if (this.progress_bar.node) { - this.progress_bar.node.style.setProperty('--progress', progress.toString()); + this.progress_bar.node.style.setProperty('--progress', progress.toString()) } }, progress: options.progress_bar.progress ?? 0, - node: null + node: null, } } this.width = options.width this.draggable = options.draggable - this.resizable = options.resizable === true ? 'xy' : options.resizable; + this.resizable = options.resizable === true ? 'xy' : options.resizable this.darken = options.darken !== false this.cancel_on_click_outside = options.cancel_on_click_outside !== false this.singleButton = options.singleButton - this.buttons = options.buttons instanceof Array ? options.buttons : (options.singleButton ? ['dialog.close'] : ['dialog.confirm', 'dialog.cancel']) - this.form_first = options.form_first; - this.confirmIndex = options.confirmIndex||0; - this.cancelIndex = options.cancelIndex !== undefined ? options.cancelIndex : this.buttons.length-1; - this.keyboard_actions = options.keyboard_actions || {}; - - this.onConfirm = options.onConfirm; - this.onCancel = options.onCancel; - this.onButton = options.onButton || options.onClose; - this.onFormChange = options.onFormChange; - this.onOpen = options.onOpen; - this.onBuild = options.onBuild; - this.onResize = options.onResize; - - this.object; + this.buttons = + options.buttons instanceof Array + ? options.buttons + : options.singleButton + ? ['dialog.close'] + : ['dialog.confirm', 'dialog.cancel'] + this.form_first = options.form_first + this.confirmIndex = options.confirmIndex || 0 + this.cancelIndex = + options.cancelIndex !== undefined ? options.cancelIndex : this.buttons.length - 1 + this.keyboard_actions = options.keyboard_actions || {} + + this.onConfirm = options.onConfirm + this.onCancel = options.onCancel + this.onButton = options.onButton || options.onClose + this.onFormChange = options.onFormChange + this.onOpen = options.onOpen + this.onBuild = options.onBuild + this.onResize = options.onResize + + this.object } /** * Triggers the confirm event of the dialog. */ confirm(event?: Event) { - this.close(this.confirmIndex, event); + this.close(this.confirmIndex, event) } /** * Triggers the cancel event of the dialog. */ cancel(event?: Event) { - this.close(this.cancelIndex, event); + this.close(this.cancelIndex, event) } updateFormValues(initial?: boolean) { - return this.form.getResult(); + return this.form.getResult() } /** * Set the values of the dialog form inputs @@ -454,7 +472,7 @@ export class Dialog { * @param update Whether to update the dialog (call onFormChange) after setting the values. Default is true. Set to false when called from onFormChange */ setFormValues(values: Record, update = true) { - this.form.setValues(values, update); + this.form.setValues(values, update) } /** * Set whether the dialog form inputs are toggled on or off. See "toggle_enabled" @@ -462,310 +480,327 @@ export class Dialog { * @param update Whether to update the dialog (call onFormChange) after setting the values. Default is true. Set to false when called from onFormChange */ setFormToggles(values: Record, update = true) { - this.form.setToggles(values, update); + this.form.setToggles(values, update) } getFormResult() { - return this.form?.getResult(); + return this.form?.getResult() } close(button: number = this.cancelIndex, event?: Event) { if (button == this.confirmIndex && typeof this.onConfirm == 'function') { - let formResult = this.getFormResult() ?? {}; - let result = this.onConfirm(formResult, event); - if (result === false) return; + let formResult = this.getFormResult() ?? {} + let result = this.onConfirm(formResult, event) + if (result === false) return } if (button == this.cancelIndex && typeof this.onCancel == 'function') { - let result = this.onCancel(event); - if (result === false) return; + let result = this.onCancel(event) + if (result === false) return } if (typeof this.onButton == 'function') { - let result = this.onButton(button, event); - if (result === false) return; + let result = this.onButton(button, event) + if (result === false) return } - this.hide(); + this.hide() } build() { - if (this.object) this.object.remove(); - this.object = document.createElement('dialog'); - this.object.className = 'dialog'; - this.object.id = this.id; - - let handle = document.createElement('div'); - handle.className = 'dialog_handle'; - this.object.append(handle); - + if (this.object) this.object.remove() + this.object = document.createElement('dialog') + this.object.className = 'dialog' + this.object.id = this.id + + let handle = document.createElement('div') + handle.className = 'dialog_handle' + this.object.append(handle) + if (this.title_menu) { - let menu_button = document.createElement('div'); - menu_button.className = 'dialog_menu_button'; - menu_button.append(Blockbench.getIconNode('expand_more')); + let menu_button = document.createElement('div') + menu_button.className = 'dialog_menu_button' + menu_button.append(Blockbench.getIconNode('expand_more')) menu_button.addEventListener('click', event => { - this.title_menu.open(menu_button); + this.title_menu.open(menu_button) }) - handle.append(menu_button); + handle.append(menu_button) } - let title = document.createElement('div'); - title.className = 'dialog_title'; - title.textContent = tl(this.title); - handle.append(title); + let title = document.createElement('div') + title.className = 'dialog_title' + title.textContent = tl(this.title) + handle.append(title) - let jq_dialog = $(this.object); - this.max_label_width = 140; - this.uses_wide_inputs = false; + let jq_dialog = $(this.object) + this.max_label_width = 140 + this.uses_wide_inputs = false - let wrapper = document.createElement('div'); - wrapper.className = 'dialog_wrapper'; + let wrapper = document.createElement('div') + wrapper.className = 'dialog_wrapper' - let content = document.createElement('content'); - content.className = 'dialog_content'; - this.object.append(wrapper); - + let content = document.createElement('content') + content.className = 'dialog_content' + this.object.append(wrapper) if (this.sidebar) { if (window.innerWidth < 920) { - let menu_button = document.createElement('div'); - menu_button.className = 'dialog_sidebar_menu_button'; - menu_button.append(Blockbench.getIconNode('menu')); + let menu_button = document.createElement('div') + menu_button.className = 'dialog_sidebar_menu_button' + menu_button.append(Blockbench.getIconNode('menu')) menu_button.addEventListener('click', event => { - this.sidebar.toggle(); + this.sidebar.toggle() }) - handle.prepend(menu_button); + handle.prepend(menu_button) } - this.sidebar.build(); - wrapper.classList.toggle('has_sidebar', this.sidebar.open); + this.sidebar.build() + wrapper.classList.toggle('has_sidebar', this.sidebar.open) } - wrapper.append(content); + wrapper.append(content) this.part_order.forEach(part => { - if (part == 'form' && this.form_config) buildForm(this); - if (part == 'lines' && this.lines) buildLines(this); - if (part == 'toolbars' && this.toolbars) buildToolbars(this); - if (part == 'component' && this.component) buildComponent(this); + if (part == 'form' && this.form_config) buildForm(this) + if (part == 'lines' && this.lines) buildLines(this) + if (part == 'toolbars' && this.toolbars) buildToolbars(this) + if (part == 'component' && this.component) buildComponent(this) }) if (this.max_label_width) { - let width = (this.width||540) + let width = this.width || 540 let max_width = this.uses_wide_inputs - ? Math.clamp(this.max_label_width+9, 0, width/2) - : Math.clamp(this.max_label_width+16, 0, width - 100); - this.object.style.setProperty('--max_label_width', max_width + 'px'); + ? Math.clamp(this.max_label_width + 9, 0, width / 2) + : Math.clamp(this.max_label_width + 16, 0, width - 100) + this.object.style.setProperty('--max_label_width', max_width + 'px') } if (this.progress_bar) { - this.progress_bar.node = Interface.createElement('div', {class: 'progress_bar'}, - Interface.createElement('div', {class: 'progress_bar_inner'}) - ) as HTMLDivElement; - this.progress_bar.setProgress(this.progress_bar.progress); - this.object.querySelector('content.dialog_content').append(this.progress_bar.node); + this.progress_bar.node = Interface.createElement( + 'div', + { class: 'progress_bar' }, + Interface.createElement('div', { class: 'progress_bar_inner' }) + ) as HTMLDivElement + this.progress_bar.setProgress(this.progress_bar.progress) + this.object.querySelector('content.dialog_content').append(this.progress_bar.node) } if (this.buttons.length) { - let buttons = [] this.buttons.forEach((b, i) => { - let btn = Interface.createElement('button', {type: 'button'}, tl(b)); - buttons.push(btn); - btn.addEventListener('click', (event) => { - this.close(i, event); + let btn = Interface.createElement('button', { type: 'button' }, tl(b)) + buttons.push(btn) + btn.addEventListener('click', event => { + this.close(i, event) }) }) buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn') buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn') - let button_bar = $('
'); + let button_bar = $('
') buttons.forEach((button, i) => { button_bar.append(button) }) - wrapper.append(button_bar[0]); + wrapper.append(button_bar[0]) } - let close_button = document.createElement('div'); - close_button.classList.add('dialog_close_button'); - close_button.innerHTML = 'clear'; - jq_dialog.append(close_button); - close_button.addEventListener('click', (e) => { - this.cancel(); + let close_button = document.createElement('div') + close_button.classList.add('dialog_close_button') + close_button.innerHTML = 'clear' + jq_dialog.append(close_button) + close_button.addEventListener('click', e => { + this.cancel() }) //Draggable if (this.draggable !== false) { jq_dialog.addClass('draggable') // @ts-ignore Draggable library doesn't have types jq_dialog.draggable({ - handle: ".dialog_handle", - containment: '#page_wrapper' + handle: '.dialog_handle', + containment: '#page_wrapper', }) jq_dialog.css('position', 'absolute') } if (this.resizable) { this.object.classList.add('resizable') - let resize_handle = Interface.createElement('div', {class: 'dialog_resize_handle'}); - jq_dialog.append(resize_handle); + let resize_handle = Interface.createElement('div', { class: 'dialog_resize_handle' }) + jq_dialog.append(resize_handle) if (this.resizable == 'x') { - resize_handle.style.cursor = 'e-resize'; + resize_handle.style.cursor = 'e-resize' } else if (this.resizable == 'y') { - resize_handle.style.cursor = 's-resize'; + resize_handle.style.cursor = 's-resize' } addEventListeners(resize_handle, 'mousedown touchstart', (e1: PointerEvent) => { - convertTouchEvent(e1); - resize_handle.classList.add('dragging'); - - let start_position = [e1.clientX, e1.clientY]; - if (!this.width) this.width = this.object.clientWidth; - let original_width = this.width; - let original_left = parseFloat(this.object.style.left); - let original_height = parseFloat(this.object.style.height) || this.object.clientHeight; + convertTouchEvent(e1) + resize_handle.classList.add('dragging') + let start_position = [e1.clientX, e1.clientY] + if (!this.width) this.width = this.object.clientWidth + let original_width = this.width + let original_left = parseFloat(this.object.style.left) + let original_height = + parseFloat(this.object.style.height) || this.object.clientHeight let move = (e2: PointerEvent) => { - convertTouchEvent(e2); - + convertTouchEvent(e2) + if (this.resizable && this.resizable.includes('x')) { - let x_offset = (e2.clientX - start_position[0]); - this.width = original_width + x_offset * 2; - this.object.style.width = this.width+'px'; + let x_offset = e2.clientX - start_position[0] + this.width = original_width + x_offset * 2 + this.object.style.width = this.width + 'px' if (this.draggable !== false) { - this.object.style.left = Math.clamp(original_left - (this.object.clientWidth - original_width) / 2, 0, window.innerWidth) + 'px'; + this.object.style.left = + Math.clamp( + original_left - (this.object.clientWidth - original_width) / 2, + 0, + window.innerWidth + ) + 'px' } } if (this.resizable && this.resizable.includes('y')) { - let y_offset = (e2.clientY - start_position[1]); - let height = Math.clamp(original_height + y_offset, 80, window.innerHeight); - this.object.style.height = height+'px'; + let y_offset = e2.clientY - start_position[1] + let height = Math.clamp(original_height + y_offset, 80, window.innerHeight) + this.object.style.height = height + 'px' } if (typeof this.onResize == 'function') { - this.onResize(); + this.onResize() } } let stop = e2 => { - removeEventListeners(document, 'mousemove touchmove', move); - removeEventListeners(document, 'mouseup touchend', stop); - resize_handle.classList.remove('dragging'); + removeEventListeners(document, 'mousemove touchmove', move) + removeEventListeners(document, 'mouseup touchend', stop) + resize_handle.classList.remove('dragging') } - addEventListeners(document, 'mousemove touchmove', move); - addEventListeners(document, 'mouseup touchend', stop); + addEventListeners(document, 'mousemove touchmove', move) + addEventListeners(document, 'mouseup touchend', stop) }) } let sanitizePosition = () => { - if (this.object.clientHeight + this.object.offsetTop - 26 > Interface.page_wrapper.clientHeight) { - this.object.style.top = Math.max(Interface.page_wrapper.clientHeight - this.object.clientHeight + 26, 26) + 'px'; + if ( + this.object.clientHeight + this.object.offsetTop - 26 > + Interface.page_wrapper.clientHeight + ) { + this.object.style.top = + Math.max( + Interface.page_wrapper.clientHeight - this.object.clientHeight + 26, + 26 + ) + 'px' } } - sanitizePosition(); + sanitizePosition() this.resize_observer = new ResizeObserver(sanitizePosition) - this.resize_observer.observe(this.object); + this.resize_observer.observe(this.object) if (typeof this.onBuild == 'function') { - this.onBuild(this.object); + this.onBuild(this.object) } - return this; + return this } private resize_observer: ResizeObserver show(anchor?: HTMLElement): this { // Hide previous // @ts-ignore Need to replace this variable still - if (window.open_interface && open_interface instanceof Dialog == false && typeof open_interface.hide == 'function') { - open_interface.hide(); + if ( + window.open_interface && + open_interface instanceof Dialog == false && + typeof open_interface.hide == 'function' + ) { + open_interface.hide() } if (!this.object) { - this.build(); + this.build() } else if (this.form) { - this.form.updateValues({cause: 'setup'}); + this.form.updateValues({ cause: 'setup' }) } - let jq_dialog = $(this.object); + let jq_dialog = $(this.object) + + document.getElementById('dialog_wrapper').append(this.object) - document.getElementById('dialog_wrapper').append(this.object); - if (this instanceof ShapelessDialog === false) { - this.object.style.display = 'flex'; - this.object.style.top = limitNumber(window.innerHeight/2-this.object.clientHeight/2, 0, 100)+'px'; + this.object.style.display = 'flex' + this.object.style.top = + limitNumber(window.innerHeight / 2 - this.object.clientHeight / 2, 0, 100) + 'px' if (this.width) { - this.object.style.width = this.width+'px'; + this.object.style.width = this.width + 'px' } if (this.draggable !== false) { - let x = Math.clamp((window.innerWidth-this.object.clientWidth)/2, 0, 2000) - this.object.style.left = x+'px'; + let x = Math.clamp((window.innerWidth - this.object.clientWidth) / 2, 0, 2000) + this.object.style.left = x + 'px' } } if (!Blockbench.isTouch) { - let first_focus = jq_dialog.find('.focusable_input').first(); - if (first_focus) first_focus.trigger('focus'); + let first_focus = jq_dialog.find('.focusable_input').first() + if (first_focus) first_focus.trigger('focus') } if (typeof this.onOpen == 'function') { - this.onOpen(); + this.onOpen() } - this.focus(); + this.focus() setTimeout(() => { - this.object.style.setProperty('--dialog-height', this.object.clientHeight + 'px'); - this.object.style.setProperty('--dialog-width', this.object.clientWidth + 'px'); - }, 1); + this.object.style.setProperty('--dialog-height', this.object.clientHeight + 'px') + this.object.style.setProperty('--dialog-width', this.object.clientWidth + 'px') + }, 1) - return this; + return this } focus() { - Dialog.stack.remove(this); - let blackout = document.getElementById('blackout'); - blackout.style.display = 'block'; - blackout.classList.toggle('darken', this.darken); - blackout.style.zIndex = (20 + Dialog.stack.length * 2).toString(); - this.object.style.zIndex = (21 + Dialog.stack.length * 2).toString(); - - Prop._previous_active_panel = Prop.active_panel; - Prop.active_panel = 'dialog'; + Dialog.stack.remove(this) + let blackout = document.getElementById('blackout') + blackout.style.display = 'block' + blackout.classList.toggle('darken', this.darken) + blackout.style.zIndex = (20 + Dialog.stack.length * 2).toString() + this.object.style.zIndex = (21 + Dialog.stack.length * 2).toString() + + Prop._previous_active_panel = Prop.active_panel + Prop.active_panel = 'dialog' // @ts-ignore - window.open_dialog = this.id; + window.open_dialog = this.id // @ts-ignore - window.open_interface = this; - Dialog.open = this; - Dialog.stack.push(this); + window.open_interface = this + Dialog.open = this + Dialog.stack.push(this) } hide() { - $('#blackout').hide().toggleClass('darken', true); - $(this.object).hide(); + $('#blackout').hide().toggleClass('darken', true) + $(this.object).hide() // @ts-ignore - window.open_dialog = false; + window.open_dialog = false // @ts-ignore - window.open_interface = false; - Dialog.open = null; - Dialog.stack.remove(this); - Prop.active_panel = Prop._previous_active_panel; - $(this.object).detach(); - + window.open_interface = false + Dialog.open = null + Dialog.stack.remove(this) + Prop.active_panel = Prop._previous_active_panel + $(this.object).detach() + if (Dialog.stack.length) { - Dialog.stack.last().focus(); + Dialog.stack.last().focus() } - return this; + return this } delete() { $(this.object).remove() if (this.content_vue) { - this.content_vue.$destroy(); - delete this.content_vue; + this.content_vue.$destroy() + delete this.content_vue } } getFormBar(form_id: string) { var bar = $(this.object).find(`.form_bar_${form_id}`) - if (bar.length) return bar; + if (bar.length) return bar } /** * Currently opened dialog */ - static open: Dialog | null = null; + static open: Dialog | null = null /** * Stack of currently open dialogs, ordered by depth */ - static stack: Dialog[] = []; + static stack: Dialog[] = [] } interface ShapelessDialogOptions { @@ -803,58 +838,62 @@ export class ShapelessDialog extends Dialog { onClose?: (event: Event) => void | boolean onConfirm?: (event: Event) => void | boolean constructor(id: string, options: ShapelessDialogOptions) { - super(id, options); + super(id, options) // @ts-ignore - if (options.build) this.build = options.build; + if (options.build) this.build = options.build // @ts-ignore - if (options.onClose) this.onClose = options.onClose; + if (options.onClose) this.onClose = options.onClose } close(button: number, event: Event) { if (button == this.confirmIndex && typeof this.onConfirm == 'function') { - let result = this.onConfirm(event); - if (result === false) return; + let result = this.onConfirm(event) + if (result === false) return } if (button == this.cancelIndex && typeof this.onCancel == 'function') { - let result = this.onCancel(event); - if (result === false) return; + let result = this.onCancel(event) + if (result === false) return } if (typeof this.onClose == 'function') { - let result = this.onClose(event); - if (result === false) return; + let result = this.onClose(event) + if (result === false) return } - this.hide(); + this.hide() } show(): this { super.show() - $(this.object).show(); - return this; + $(this.object).show() + return this } build(): this { - this.object = Interface.createElement('div', {id: this.id, class: 'shapeless_dialog'}); + this.object = Interface.createElement('div', { id: this.id, class: 'shapeless_dialog' }) if (this.component) { - this.component.name = 'dialog-content'; - this.content_vue = new Vue(this.component).$mount(this.object, true); + this.component.name = 'dialog-content' + this.content_vue = new Vue(this.component).$mount(this.object, true) } - return this; + return this } delete() { if (this.object) this.object.remove() - this.object = null; + this.object = null } } -type MessageBoxCommandOptions = string | { - text: string - icon?: IconString - condition?: ConditionResolvable - description?: string -} -type MessageBoxCheckbox = string | { - value?: boolean - condition: ConditionResolvable - text: string -} +type MessageBoxCommandOptions = + | string + | { + text: string + icon?: IconString + condition?: ConditionResolvable + description?: string + } +type MessageBoxCheckbox = + | string + | { + value?: boolean + condition: ConditionResolvable + text: string + } export interface MessageBoxOptions { /** * Index of the confirm button within the buttons array @@ -884,121 +923,164 @@ export interface MessageBoxOptions { export class MessageBox extends Dialog { // @ts-ignore We should rewrite this to use a common internal DialogBase class declare configuration: MessageBoxOptions - callback?: (button: number | string, result?: Record, event?: Event) => void |boolean - - constructor(options: MessageBoxOptions, callback?: (button: number | string, result?: Record, event?: Event) => void |boolean) { - super('message_box', options as DialogOptions); - this.configuration = options; - if (!options.buttons) this.buttons = ['dialog.ok']; - this.cancelIndex = Math.min(this.buttons.length-1, this.cancelIndex); - this.confirmIndex = Math.min(this.buttons.length-1, this.confirmIndex); - this.callback = callback; + callback?: ( + button: number | string, + result?: Record, + event?: Event + ) => void | boolean + + constructor( + options: MessageBoxOptions, + callback?: ( + button: number | string, + result?: Record, + event?: Event + ) => void | boolean + ) { + super('message_box', options as DialogOptions) + this.configuration = options + if (!options.buttons) this.buttons = ['dialog.ok'] + this.cancelIndex = Math.min(this.buttons.length - 1, this.cancelIndex) + this.confirmIndex = Math.min(this.buttons.length - 1, this.confirmIndex) + this.callback = callback } // @ts-ignore close(button: number | string, result: Record, event: Event) { if (this.callback) { - let allow_close = this.callback(button, result, event); - if (allow_close === false) return; + let allow_close = this.callback(button, result, event) + if (allow_close === false) return } - this.hide(); - this.delete(); + this.hide() + this.delete() } build(): this { - let options = this.configuration; + let options = this.configuration - let results: Record; + let results: Record if (options.translateKey) { - if (!options.title) options.title = tl('message.'+options.translateKey+'.title') - if (!options.message) options.message = tl('message.'+options.translateKey+'.message') + if (!options.title) options.title = tl('message.' + options.translateKey + '.title') + if (!options.message) + options.message = tl('message.' + options.translateKey + '.message') } - let content = Interface.createElement('div', {class: 'dialog_content'}); - this.object = Interface.createElement('dialog', {class: 'dialog', style: 'width: auto;', id: 'message_box'}, [ - Interface.createElement('div', {class: 'dialog_handle'}, Interface.createElement('div', {class: 'dialog_title'}, tl(options.title))), - Interface.createElement('div', {class: 'dialog_close_button', onclick: 'Dialog.open.cancel()'}, Blockbench.getIconNode('clear')), - content - ]); - let jq_dialog = $(this.object); + let content = Interface.createElement('div', { class: 'dialog_content' }) + this.object = Interface.createElement( + 'dialog', + { class: 'dialog', style: 'width: auto;', id: 'message_box' }, + [ + Interface.createElement( + 'div', + { class: 'dialog_handle' }, + Interface.createElement('div', { class: 'dialog_title' }, tl(options.title)) + ), + Interface.createElement( + 'div', + { class: 'dialog_close_button', onclick: 'Dialog.open.cancel()' }, + Blockbench.getIconNode('clear') + ), + content, + ] + ) + let jq_dialog = $(this.object) if (options.message) { - content.append($(`
`+ - pureMarked(tl(options.message))+ - '
')[0]); + content.append( + $( + `
` + + pureMarked(tl(options.message)) + + '
' + )[0] + ) } if (options.icon) { - let bar = jq_dialog.find('.dialog_bar'); - bar.prepend($(Blockbench.getIconNode(options.icon)).addClass('message_box_icon')); - bar.append('
'); + let bar = jq_dialog.find('.dialog_bar') + bar.prepend($(Blockbench.getIconNode(options.icon)).addClass('message_box_icon')) + bar.append('
') } if (options.commands) { - let list = Interface.createElement('ul'); + let list = Interface.createElement('ul') for (let id in options.commands) { - let command = options.commands[id]; - if (!command || (typeof command == 'object' && !Condition(command.condition))) continue; - let text = tl(typeof command == 'string' ? command : command.text); - let entry = Interface.createElement('li', {class: 'dialog_message_box_command'}, text); + let command = options.commands[id] + if (!command || (typeof command == 'object' && !Condition(command.condition))) + continue + let text = tl(typeof command == 'string' ? command : command.text) + let entry = Interface.createElement( + 'li', + { class: 'dialog_message_box_command' }, + text + ) if (typeof command == 'object') { if (command.icon) { - entry.prepend(Blockbench.getIconNode(command.icon)); + entry.prepend(Blockbench.getIconNode(command.icon)) } if (command.description) { - let label = Interface.createElement('label', {}, tl(command.description)); - entry.append(label); + let label = Interface.createElement('label', {}, tl(command.description)) + entry.append(label) } } entry.addEventListener('click', e => { - this.close(id, results, e); + this.close(id, results, e) }) - list.append(entry); + list.append(entry) } - content.append(list); + content.append(list) } if (options.checkboxes) { - let list = Interface.createElement('ul', {class: 'dialog_message_box_checkboxes'}); - results = {}; + let list = Interface.createElement('ul', { class: 'dialog_message_box_checkboxes' }) + results = {} for (let id in options.checkboxes) { - let checkbox = options.checkboxes[id]; - results[id] = false; + let checkbox = options.checkboxes[id] + results[id] = false if (typeof checkbox == 'object') { - results[id] = !!checkbox.value; - if (!Condition(checkbox.condition)) continue; + results[id] = !!checkbox.value + if (!Condition(checkbox.condition)) continue } - let text = tl(typeof checkbox == 'string' ? checkbox : checkbox.text); - let entry = Interface.createElement('li', {class: 'dialog_message_box_checkbox'}, [ - Interface.createElement('input', {type: 'checkbox', id: 'dialog_message_box_checkbox_'+id}), - Interface.createElement('label', {for: 'dialog_message_box_checkbox_'+id, checked: results[id]}, text) - ]) + let text = tl(typeof checkbox == 'string' ? checkbox : checkbox.text) + let entry = Interface.createElement( + 'li', + { class: 'dialog_message_box_checkbox' }, + [ + Interface.createElement('input', { + type: 'checkbox', + id: 'dialog_message_box_checkbox_' + id, + }), + Interface.createElement( + 'label', + { for: 'dialog_message_box_checkbox_' + id, checked: results[id] }, + text + ), + ] + ) entry.firstElementChild.addEventListener('change', e => { - results[id] = (e.target as HTMLInputElement).checked; + results[id] = (e.target as HTMLInputElement).checked }) - list.append(entry); + list.append(entry) } - content.append(list); + content.append(list) } // Buttons if (this.buttons.length) { - let buttons = [] this.buttons.forEach((b, i) => { - let btn = Interface.createElement('button', {type: 'button'}, tl(b)); - buttons.push(btn); - btn.addEventListener('click', (event) => { - this.close(i, results, event); + let btn = Interface.createElement('button', { type: 'button' }, tl(b)) + buttons.push(btn) + btn.addEventListener('click', event => { + this.close(i, results, event) }) }) buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn') buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn') - let button_bar = $('
'); + let button_bar = $('
') buttons.forEach((button, i) => { button_bar.append(button) }) - jq_dialog.append(button_bar[0]); + jq_dialog.append(button_bar[0]) } //Draggable @@ -1006,108 +1088,118 @@ export class MessageBox extends Dialog { jq_dialog.addClass('draggable') // @ts-ignore jq_dialog.draggable({ - handle: ".dialog_handle", - containment: '#page_wrapper' + handle: '.dialog_handle', + containment: '#page_wrapper', }) - this.object.style.position = 'absolute'; + this.object.style.position = 'absolute' } - let x = (window.innerWidth-540)/2 - this.object.style.left = x+'px'; - this.object.style.position = 'absolute'; + let x = (window.innerWidth - 540) / 2 + this.object.style.left = x + 'px' + this.object.style.position = 'absolute' - this.object.style.top = limitNumber(window.innerHeight/2-jq_dialog.height()/2 - 140, 0, 2000)+'px'; + this.object.style.top = + limitNumber(window.innerHeight / 2 - jq_dialog.height() / 2 - 140, 0, 2000) + 'px' if (options.width) { - this.object.style.width = options.width+'px' + this.object.style.width = options.width + 'px' } else { - this.object.style.width = limitNumber((options.buttons ? options.buttons.length : 1) * 170+44, 380, 894)+'px'; + this.object.style.width = + limitNumber((options.buttons ? options.buttons.length : 1) * 170 + 44, 380, 894) + + 'px' } - return this; + return this } delete() { if (this.object) this.object.remove() - this.object = null; + this.object = null } } -interface ConfigDialogOptions extends DialogOptions { - -} +interface ConfigDialogOptions extends DialogOptions {} export class ConfigDialog extends Dialog { constructor(id: string, options: ConfigDialogOptions) { - super(id, options); + super(id, options) } show(anchor: HTMLElement) { super.show() - $('#blackout').hide(); - + $('#blackout').hide() + if (anchor instanceof HTMLElement) { - let anchor_position = $(anchor).offset(); - this.object.style.top = (anchor_position.top+anchor.offsetHeight) + 'px'; - this.object.style.left = Math.clamp(anchor_position.left - 30, 0, window.innerWidth-this.object.clientWidth - (this.title ? 0 : 30)) + 'px'; + let anchor_position = $(anchor).offset() + this.object.style.top = anchor_position.top + anchor.offsetHeight + 'px' + this.object.style.left = + Math.clamp( + anchor_position.left - 30, + 0, + window.innerWidth - this.object.clientWidth - (this.title ? 0 : 30) + ) + 'px' } - return this; + return this } build() { - if (this.object) this.object.remove(); - this.object = document.createElement('dialog'); - this.object.className = 'dialog config_dialog'; + if (this.object) this.object.remove() + this.object = document.createElement('dialog') + this.object.className = 'dialog config_dialog' - this.max_label_width = 140; - this.uses_wide_inputs = false; + this.max_label_width = 140 + this.uses_wide_inputs = false - let title_bar; + let title_bar if (this.title) { - title_bar = Interface.createElement('div', {class: 'config_dialog_title'}, tl(this.title)); - this.object.append(title_bar); + title_bar = Interface.createElement( + 'div', + { class: 'config_dialog_title' }, + tl(this.title) + ) + this.object.append(title_bar) } - let wrapper = document.createElement('div'); - wrapper.className = 'dialog_wrapper'; + let wrapper = document.createElement('div') + wrapper.className = 'dialog_wrapper' + + let content = document.createElement('content') + content.className = 'dialog_content' + this.object.append(wrapper) - let content = document.createElement('content'); - content.className = 'dialog_content'; - this.object.append(wrapper); - - wrapper.append(content); + wrapper.append(content) - this.form = new InputForm(this.form_config); - content.append(this.form.node); - this.max_label_width = Math.max(this.max_label_width, this.form.max_label_width); - if (this.form.uses_wide_inputs) this.uses_wide_inputs = true; - this.form.on('change', ({result}) => { + this.form = new InputForm(this.form_config) + content.append(this.form.node) + this.max_label_width = Math.max(this.max_label_width, this.form.max_label_width) + if (this.form.uses_wide_inputs) this.uses_wide_inputs = true + this.form.on('change', ({ result }) => { if (this.configuration) { for (let key in result) { - this.configuration[key] = result[key]; + this.configuration[key] = result[key] } } - if (this.onFormChange) this.onFormChange(result); + if (this.onFormChange) this.onFormChange(result) }) if (this.toolbars) { - buildToolbars(this); + buildToolbars(this) } - let close_button = document.createElement('div'); - close_button.classList.add('dialog_close_button'); - close_button.innerHTML = 'clear'; - close_button.addEventListener('click', (e) => { - this.cancel(); + let close_button = document.createElement('div') + close_button.classList.add('dialog_close_button') + close_button.innerHTML = 'clear' + close_button.addEventListener('click', e => { + this.cancel() }) if (title_bar) { - title_bar.append(close_button); + title_bar.append(close_button) } else { - this.object.append(close_button); + this.object.append(close_button) } if (typeof this.onBuild == 'function') { - this.onBuild(this.object); + this.onBuild(this.object) } - return this; + return this } delete() { if (this.object) this.object.remove() - this.object = null; + this.object = null } } export class ToolConfig extends ConfigDialog { @@ -1115,24 +1207,25 @@ export class ToolConfig extends ConfigDialog { [key: string]: FormResultValue } constructor(id: string, options: ConfigDialogOptions) { - super(id, options); + super(id, options) - this.options = {}; - let config_saved_data: Record; + this.options = {} + let config_saved_data: Record try { - let stored = localStorage.getItem(`tool_config.${this.id}`);; - config_saved_data = JSON.parse(stored); - if (!config_saved_data) config_saved_data = {}; + let stored = localStorage.getItem(`tool_config.${this.id}`) + config_saved_data = JSON.parse(stored) + if (!config_saved_data) config_saved_data = {} } catch (err) { - config_saved_data = {}; + config_saved_data = {} } for (let key in options.form) { - if (options.form[key] == '_') continue; + if (options.form[key] == '_') continue if (key == 'enabled' && BarItem.constructing instanceof Toggle) { - this.options[key] = BarItem.constructing.value; - continue; + this.options[key] = BarItem.constructing.value + continue } - this.options[key] = config_saved_data[key] ?? InputForm.getDefaultValue(options.form[key]); + this.options[key] = + config_saved_data[key] ?? InputForm.getDefaultValue(options.form[key]) } } /** @@ -1140,19 +1233,16 @@ export class ToolConfig extends ConfigDialog { * @param anchor Optional element to anchor the menu to */ show(anchor?: HTMLElement): this { - super.show(anchor); - this.setFormValues(this.options, false); - this.form.on('input', ({result, cause}) => { - this.changeOptions(result) - }) - return this; + super.show(anchor) + this.setFormValues(this.options, false) + return this } /** * Save any changes in local storage */ save() { - localStorage.setItem(`tool_config.${this.id}`, JSON.stringify(this.options)); - return this; + localStorage.setItem(`tool_config.${this.id}`, JSON.stringify(this.options)) + return this } /** * Change and save a number of options in the config @@ -1160,21 +1250,20 @@ export class ToolConfig extends ConfigDialog { */ changeOptions(options: Record): this { for (let key in options) { - this.options[key] = options[key]; + this.options[key] = options[key] } if (this.form) { - this.form.setValues(options); + this.form.setValues(options) } - this.save(); - return this; + this.save() + return this } close() { - this.save(); - this.hide(); + this.save() + this.hide() } } - Object.assign(window, { DialogSidebar, Dialog, @@ -1182,4 +1271,4 @@ Object.assign(window, { MessageBox, ConfigDialog, ToolConfig, -}); +}) diff --git a/js/interface/form.ts b/js/interface/form.ts index 3ea4cd9e1..d0fc923b5 100644 --- a/js/interface/form.ts +++ b/js/interface/form.ts @@ -1,10 +1,10 @@ -import { Blockbench } from "../api" -import { Clipbench } from "../copy_paste" -import { Filesystem } from "../file_system" -import { tl } from "../languages" -import { EventSystem } from "../util/event_system" -import { getStringWidth, pureMarked } from "../util/util" -import { Interface } from "./interface" +import { Blockbench } from '../api' +import { Clipbench } from '../copy_paste' +import { Filesystem } from '../file_system' +import { tl } from '../languages' +import { EventSystem } from '../util/event_system' +import { getStringWidth, pureMarked } from '../util/util' +import { Interface } from './interface' type ReadType = 'buffer' | 'binary' | 'text' | 'image' interface FileResult { @@ -103,7 +103,9 @@ export interface FormElementOptions { /** * Available options on select or inline_select inputs */ - options?: Record | (() => Record) + options?: + | Record + | (() => Record) /** * List of buttons for the button type */ @@ -153,7 +155,6 @@ export type InputFormConfig = { } type FormValues = Record - // MARK: InputForm export class InputForm extends EventSystem { uuid: string @@ -164,116 +165,137 @@ export class InputForm extends EventSystem { uses_wide_inputs: boolean constructor(form_config: InputFormConfig, options = {}) { - super(); - this.uuid = guid(); - this.form_config = form_config; - this.form_data = {}; - this.node = Interface.createElement('div', {class: 'form'}) as HTMLDivElement; - this.max_label_width = 0; - this.uses_wide_inputs = false; + super() + this.uuid = guid() + this.form_config = form_config + this.form_data = {} + this.node = Interface.createElement('div', { class: 'form' }) as HTMLDivElement + this.max_label_width = 0 + this.uses_wide_inputs = false - this.buildForm(); - this.updateValues({cause: 'setup'}); + this.buildForm() + this.updateValues({ cause: 'setup' }) } buildForm() { - let jq_node = $(this.node); - jq_node.empty(); + let jq_node = $(this.node) + jq_node.empty() for (let form_id in this.form_config) { - let input_config = this.form_config[form_id]; - form_id = form_id.replace(/"/g, ''); + let input_config = this.form_config[form_id] + form_id = form_id.replace(/"/g, '') if (input_config === '_') { - jq_node.append('
'); - continue; + jq_node.append('
') + continue } - let InputType = FormElement.types[input_config.type] ?? FormElement.types.text; - let form_element = this.form_data[form_id] = new InputType(form_id, input_config, this); - let bar = Interface.createElement('div', {class: `dialog_bar bar form_bar form_bar_${form_id}`}); - form_element.build(bar); - form_element.setup(); - if (form_element.uses_wide_inputs) this.uses_wide_inputs = true; - jq_node.append(bar); + let InputType = FormElement.types[input_config.type] ?? FormElement.types.text + let form_element = (this.form_data[form_id] = new InputType( + form_id, + input_config, + this + )) + let bar = Interface.createElement('div', { + class: `dialog_bar bar form_bar form_bar_${form_id}`, + }) + form_element.build(bar) + form_element.setup() + if (form_element.uses_wide_inputs) this.uses_wide_inputs = true + jq_node.append(bar) } - this.updateLabelWidth(); + this.updateLabelWidth() } updateLabelWidth(ignore_hidden: boolean = false) { - this.max_label_width = 0; + this.max_label_width = 0 for (let form_id in this.form_config) { - form_id = form_id.replace(/"/g, ''); - let form_element = this.form_data[form_id]; - if (!form_element || (ignore_hidden && !Condition(form_element.condition))) continue; + form_id = form_id.replace(/"/g, '') + let form_element = this.form_data[form_id] + if (!form_element || (ignore_hidden && !Condition(form_element.condition))) continue - if (form_element.uses_wide_inputs) this.uses_wide_inputs = true; - if (form_element.label_width) { - this.max_label_width = Math.max(form_element.label_width, this.max_label_width); - } + if (form_element.uses_wide_inputs) this.uses_wide_inputs = true + this.max_label_width = Math.max(form_element.label_width, this.max_label_width) } - this.node.style.setProperty('--max_label_width', this.max_label_width+'px'); + this.node.style.setProperty('--max_label_width', this.max_label_width + 'px') } update(form_result: FormValues) { for (let form_id in this.form_config) { - let form_element = this.form_data[form_id]; - let input_config = this.form_config[form_id]; + let form_element = this.form_data[form_id] + let input_config = this.form_config[form_id] if (typeof input_config == 'object' && form_element.bar) { - let show = Condition(input_config.condition, form_result); - form_element.bar.style.display = show ? null : 'none'; + let show = Condition(input_config.condition, form_result) + form_element.bar.style.display = show ? null : 'none' } } } - updateValues(context: {cause?: string, changed_keys?: string[]} = {}) { - let form_result = this.getResult(); - this.update(form_result); + updateValues(context: { cause?: string; changed_keys?: string[] } = {}) { + let form_result = this.getResult() + this.update(form_result) if (context.cause != 'setup') { - this.dispatchEvent('change', {result: form_result, cause: context.cause, changed_keys: context.changed_keys}); + this.dispatchEvent('change', { + result: form_result, + cause: context.cause, + changed_keys: context.changed_keys, + }) } if (context.cause == 'input') { - this.dispatchEvent('input', {result: form_result, changed_keys: context.changed_keys}); + this.dispatchEvent('input', { result: form_result, changed_keys: context.changed_keys }) } - return form_result; + return form_result } setValues(values: FormValues, update = true) { for (let form_id in this.form_config) { - let form_element = this.form_data[form_id]; - let input_config = this.form_config[form_id]; - if (form_element && 'setValue' in form_element && values[form_id] != undefined && typeof input_config == 'object') { - form_element.setValue(values[form_id]); + let form_element = this.form_data[form_id] + let input_config = this.form_config[form_id] + if ( + form_element && + 'setValue' in form_element && + values[form_id] != undefined && + typeof input_config == 'object' + ) { + form_element.setValue(values[form_id]) } } - if (update) this.updateValues({cause: 'update_value'}); + if (update) this.updateValues({ cause: 'update_value' }) } setToggles(values: Record, update = true) { for (let form_id in this.form_config) { - let input_config = this.form_config[form_id]; - let form_element = this.form_data[form_id]; - if (values[form_id] != undefined && typeof input_config == 'object' && form_element.input_toggle && form_element.bar) { - form_element.input_toggle.checked = values[form_id]; - form_element.bar.classList.toggle('form_toggle_disabled', !form_element.input_toggle.checked); + let input_config = this.form_config[form_id] + let form_element = this.form_data[form_id] + if ( + values[form_id] != undefined && + typeof input_config == 'object' && + form_element.input_toggle && + form_element.bar + ) { + form_element.input_toggle.checked = values[form_id] + form_element.bar.classList.toggle( + 'form_toggle_disabled', + !form_element.input_toggle.checked + ) } } - if (update) this.updateValues({cause: 'update_toggle'}); + if (update) this.updateValues({ cause: 'update_toggle' }) } getResult(): FormValues { let result = {} for (let form_id in this.form_data) { - let form_element = this.form_data[form_id]; + let form_element = this.form_data[form_id] if (form_element) { if (form_element.input_toggle && form_element.input_toggle.checked == false) { - result[form_id] = null; - continue; + result[form_id] = null + continue } else { - result[form_id] = form_element.getValue(); + result[form_id] = form_element.getValue() } } } - return result; + return result } static getDefaultValue(input_config: FormElementOptions): FormResultValue { - let set_value = input_config.value ?? input_config.default; - if (set_value) return set_value; - let type = FormElement.types[input_config.type]; + let set_value = input_config.value ?? input_config.default + if (set_value) return set_value + let type = FormElement.types[input_config.type] if (type) { - return type.prototype.getDefault.call({options: input_config}); + return type.prototype.getDefault.call({ options: input_config }) } - return ''; + return '' } } @@ -287,643 +309,714 @@ export class FormElement extends EventSystem { input_toggle?: HTMLInputElement label_width: number constructor(id: string, options: FormElementOptions, form: InputForm) { - super(); - this.id = id; - this.options = options; - this.form = form; - this.condition = options.condition; + super() + this.id = id + this.options = options + this.form = form + this.condition = options.condition } build(bar: HTMLDivElement) { - this.bar = bar; + this.bar = bar if (typeof this.options.label == 'string') { - let label = Interface.createElement('label', {class: 'name_space_left', for: this.id}, tl(this.options.label)) - bar.append(label); - if (!this.options.full_width && this.condition !== false/*Weed out inputs where the condition is always false*/) { - this.label_width = getStringWidth(label.textContent); + let label = Interface.createElement( + 'label', + { class: 'name_space_left', for: this.id }, + tl(this.options.label) + ) + bar.append(label) + if ( + !this.options.full_width && + this.condition !== false /*Weed out inputs where the condition is always false*/ + ) { + this.label_width = getStringWidth(label.textContent) } } if (this.options.full_width) { - bar.classList.add('full_width_dialog_bar'); + bar.classList.add('full_width_dialog_bar') } if (this.options.description) { - bar.setAttribute('title', tl(this.options.description)); + bar.setAttribute('title', tl(this.options.description)) } } get uses_wide_inputs() { - return this.options.full_width; + return this.options.full_width } getValue(): any { - return this.getDefault(); - } - setValue(value: any) { + return this.getDefault() } + setValue(value: any) {} getDefault(): any { - return null; + return null } change() { - this.dispatchEvent('change', {changed_keys: [this.id]}); - this.form.updateValues({cause: 'input', changed_keys: [this.id]}); + this.dispatchEvent('change', { changed_keys: [this.id] }) + this.form.updateValues({ cause: 'input', changed_keys: [this.id] }) } setup() { if (this.options.readonly && 'input' in this) { - $(this.input).attr('readonly', 'readonly').removeClass('focusable_input'); + $(this.input).attr('readonly', 'readonly').removeClass('focusable_input') } if (this.options.description) { - let icon = document.createElement('i'); - icon.className = 'fa fa-question dialog_form_description'; + let icon = document.createElement('i') + icon.className = 'fa fa-question dialog_form_description' icon.onclick = () => { - Blockbench.showQuickMessage(this.options.description, 3600); + Blockbench.showQuickMessage(this.options.description, 3600) } - this.bar.append(icon); + this.bar.append(icon) } if (this.options.toggle_enabled) { let toggle = Interface.createElement('input', { type: 'checkbox', class: 'focusable_input form_input_toggle', id: this.id + '_toggle', - }) as HTMLInputElement; - toggle.checked = this.options.toggle_default != false; - this.bar.append(toggle); - this.bar.classList.toggle('form_toggle_disabled', !toggle.checked); + }) as HTMLInputElement + toggle.checked = this.options.toggle_default != false + this.bar.append(toggle) + this.bar.classList.toggle('form_toggle_disabled', !toggle.checked) toggle.addEventListener('input', () => { - this.change(); - this.bar.classList.toggle('form_toggle_disabled', !toggle.checked); - }); - this.input_toggle = toggle; + this.change() + this.bar.classList.toggle('form_toggle_disabled', !toggle.checked) + }) + this.input_toggle = toggle } } - static types: Record = {}; + static types: Record = {} static registerType(id: string, type_class: typeof FormElement) { - FormElement.types[id] = type_class; + FormElement.types[id] = type_class } } // MARK: FormElement Types FormElement.types.range = class FormElementRange extends FormElement { input: HTMLInputElement - numeric_input?: {value: number} + numeric_input?: { value: number } build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this let input_element = $(``) - bar.append(input_element[0]); - this.input = input_element[0] as HTMLInputElement; + value="${this.options.value || 0}" min="${this.options.min}" max="${this.options.max}" step="${this.options.step || 1}">`) + bar.append(input_element[0]) + this.input = input_element[0] as HTMLInputElement if (!this.options.editable_range_label) { - let display = Interface.createElement('span', {class: 'range_input_label'}, (this.options.value||0).toString()) - bar.append(display); + let display = Interface.createElement( + 'span', + { class: 'range_input_label' }, + (this.options.value || 0).toString() + ) + bar.append(display) input_element.on('input', () => { - let result = this.form.getResult(); - display.textContent = trimFloatNumber(result[this.id] as number); + let result = this.form.getResult() + display.textContent = trimFloatNumber(result[this.id] as number) }) } else { - bar.classList.add('slider_input_combo'); + bar.classList.add('slider_input_combo') let numeric_input = new Interface.CustomElements.NumericInput(this.id + '_number', { value: this.options.value ?? 0, - min: this.options.min, max: this.options.max, step: this.options.step, + min: this.options.min, + max: this.options.max, + step: this.options.step, onChange() { - input_element.val(numeric_input.value); - scope.change(); - } - }); - this.numeric_input = numeric_input; - bar.append(numeric_input.node); + input_element.val(numeric_input.value) + scope.change() + }, + }) + this.numeric_input = numeric_input + bar.append(numeric_input.node) input_element.on('input', () => { - let result = parseFloat(input_element.val() as string); - numeric_input.value = result; + let result = parseFloat(input_element.val() as string) + numeric_input.value = result }) } input_element.on('input', () => { - this.change(); + this.change() }) } getValue(): number { - let result: number; + let result: number if (this.options.editable_range_label && this.numeric_input) { - result = this.numeric_input.value; + result = this.numeric_input.value } else { - result = parseFloat(this.input.value); + result = parseFloat(this.input.value) } if (this.options.force_step && this.options.step) { - result = Math.round(result / this.options.step) * this.options.step; + result = Math.round(result / this.options.step) * this.options.step } - return Math.clamp(result||0, this.options.min, this.options.max); + return Math.clamp(result || 0, this.options.min, this.options.max) } setValue(value: number): void { - this.input.value = value.toString(); + this.input.value = value.toString() } getDefault(): number { - return Math.clamp(0, this.options.min, this.options.max); + return Math.clamp(0, this.options.min, this.options.max) } -}; +} FormElement.types.info = class FormElementInfo extends FormElement { get uses_wide_inputs(): boolean { - return true; + return true } build(bar: HTMLDivElement) { - this.bar = bar; + this.bar = bar if (typeof this.options.label == 'string') { - let label = Interface.createElement('label', {class: 'name_space_left', for: this.id}, tl(this.options.label)) - bar.append(label); - if (!this.options.full_width && this.condition !== false/*Weed out inputs where the condition is always false*/) { - this.label_width = getStringWidth(label.textContent); + let label = Interface.createElement( + 'label', + { class: 'name_space_left', for: this.id }, + tl(this.options.label) + ) + bar.append(label) + if ( + !this.options.full_width && + this.condition !== false /*Weed out inputs where the condition is always false*/ + ) { + this.label_width = getStringWidth(label.textContent) } } if (this.options.full_width) { - bar.classList.add('full_width_dialog_bar'); + bar.classList.add('full_width_dialog_bar') } if (this.options.description) { - bar.setAttribute('title', tl(this.options.description)); + bar.setAttribute('title', tl(this.options.description)) } - let content = Interface.createElement('div', {class: 'small_text'}); - content.innerHTML = pureMarked(tl(this.options.text)); - bar.append(content); + let content = Interface.createElement('div', { class: 'small_text' }) + content.innerHTML = pureMarked(tl(this.options.text)) + bar.append(content) } -}; +} FormElement.types.text = class FormElementText extends FormElement { password_toggle?: HTMLElement input: HTMLInputElement build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this let input_element = Object.assign(document.createElement('input'), { type: 'text', className: 'dark_bordered half focusable_input', id: this.id, - value: this.options.value||'', - placeholder: this.options.placeholder||'', + value: this.options.value || '', + placeholder: this.options.placeholder || '', oninput() { - scope.change(); - } - }); - this.input = input_element; + scope.change() + }, + }) + this.input = input_element bar.append(input_element) if (this.options.list) { - let list_id = `${this.form.uuid}_${this.id}_list`; - input_element.setAttribute('list', list_id); - let list = Interface.createElement('datalist', {id: list_id}); + let list_id = `${this.form.uuid}_${this.id}_list` + input_element.setAttribute('list', list_id) + let list = Interface.createElement('datalist', { id: list_id }) for (let value of this.options.list) { - let node = document.createElement('option'); - node.value = value; - list.append(node); + let node = document.createElement('option') + node.value = value + list.append(node) } - bar.append(list[0]); + bar.append(list[0]) } if (this.options.type == 'password') { - bar.append(`
`) - input_element.type = 'password'; - let hidden = true; - let this_bar = $(bar); - let this_input_element = input_element; + input_element.type = 'password' + let hidden = true + let this_bar = $(bar) + let this_input_element = input_element this_bar.find('.password_toggle').on('click', e => { - hidden = !hidden; - this_input_element.setAttribute('type', hidden ? 'password' : 'text'); - this_bar.find('.password_toggle i')[0].className = hidden ? 'fas fa-eye-slash' : 'fas fa-eye'; + hidden = !hidden + this_input_element.setAttribute('type', hidden ? 'password' : 'text') + this_bar.find('.password_toggle i')[0].className = hidden + ? 'fas fa-eye-slash' + : 'fas fa-eye' }) } if (this.options.share_text && this.options.value) { - let text = this.options.value.toString(); - let is_url = text.startsWith('https://'); + let text = this.options.value.toString() + let is_url = text.startsWith('https://') - let copy_button = Interface.createElement('div', {class: 'form_input_tool tool', title: tl('dialog.copy_to_clipboard')}, Blockbench.getIconNode('content_paste')); + let copy_button = Interface.createElement( + 'div', + { class: 'form_input_tool tool', title: tl('dialog.copy_to_clipboard') }, + Blockbench.getIconNode('content_paste') + ) copy_button.addEventListener('click', e => { if (isApp || navigator.clipboard) { - Clipbench.setText(text); - Blockbench.showQuickMessage('dialog.copied_to_clipboard'); - input_element.focus(); - document.execCommand('selectAll'); - + Clipbench.setText(text) + Blockbench.showQuickMessage('dialog.copied_to_clipboard') + input_element.focus() + document.execCommand('selectAll') } else if (is_url) { Blockbench.showMessageBox({ title: 'dialog.share_model.title', message: `[${text}](${text})`, }) } - }); - bar.append(copy_button); + }) + bar.append(copy_button) if (is_url) { - let open_button = Interface.createElement('div', {class: 'form_input_tool tool', title: tl('dialog.open_url')}, Blockbench.getIconNode('open_in_browser')); + let open_button = Interface.createElement( + 'div', + { class: 'form_input_tool tool', title: tl('dialog.open_url') }, + Blockbench.getIconNode('open_in_browser') + ) open_button.addEventListener('click', e => { - Blockbench.openLink(text); - }); - bar.append(open_button); + Blockbench.openLink(text) + }) + bar.append(open_button) } if (navigator.share) { - let share_button = Interface.createElement('div', {class: 'form_input_tool tool', title: tl('generic.share')}, Blockbench.getIconNode('share')); + let share_button = Interface.createElement( + 'div', + { class: 'form_input_tool tool', title: tl('generic.share') }, + Blockbench.getIconNode('share') + ) share_button.addEventListener('click', e => { navigator.share({ title: this.options.label ? tl(this.options.label) : 'Share', - [is_url ? 'url' : 'text']: text - }); - }); - bar.append(share_button); + [is_url ? 'url' : 'text']: text, + }) + }) + bar.append(share_button) } } } getValue(): string { - return this.input.value; + return this.input.value } setValue(value: string) { - this.input.value = value; + this.input.value = value } getDefault(): string { - return ''; + return '' } -}; +} FormElement.types.textarea = class FormElementTextarea extends FormElement { textarea: HTMLTextAreaElement build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this this.textarea = Object.assign(document.createElement('textarea'), { className: 'focusable_input', id: this.id, - value: this.options.value||'', - placeholder: this.options.placeholder||'', + value: this.options.value || '', + placeholder: this.options.placeholder || '', oninput() { - scope.change(); - } - }); - this.textarea.style.height = (this.options.height || 150) + 'px'; - bar.append(this.textarea); + scope.change() + }, + }) + this.textarea.style.height = (this.options.height || 150) + 'px' + bar.append(this.textarea) } getValue() { - return this.textarea.value; + return this.textarea.value } setValue(value: string) { - this.textarea.value = value; + this.textarea.value = value } getDefault(): string { - return ''; + return '' } -}; +} FormElement.types.number = class FormElementNumber extends FormElement { - numeric_input: {value: number, node: HTMLElement} + numeric_input: { value: number; node: HTMLElement } build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this this.numeric_input = new Interface.CustomElements.NumericInput(this.id, { value: this.options.value, - min: this.options.min, max: this.options.max, step: this.options.step, + min: this.options.min, + max: this.options.max, + step: this.options.step, onChange() { - scope.change(); - } - }); + scope.change() + }, + }) bar.append(this.numeric_input.node) } getValue() { - let result = Math.clamp(this.numeric_input.value, this.options.min, this.options.max); + let result = Math.clamp(this.numeric_input.value, this.options.min, this.options.max) if (this.options.force_step && this.options.step) { - result = Math.round(result / this.options.step) * this.options.step; + result = Math.round(result / this.options.step) * this.options.step } - return result; + return result } setValue(value: number) { - this.numeric_input.value = value; + this.numeric_input.value = value } getDefault(): number { - return Math.clamp(0, this.options.min, this.options.max); + return Math.clamp(0, this.options.min, this.options.max) } -}; +} FormElement.types.select = class FormElementSelect extends FormElement { - select_input: {node: HTMLElement, set: (value: string) => void} + select_input: { node: HTMLElement; set: (value: string) => void } build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this this.select_input = new Interface.CustomElements.SelectInput(this.id, { options: this.options.options, value: this.options.value || this.options.default, onInput() { - scope.change(); - } - }); - bar.append(this.select_input.node); + scope.change() + }, + }) + bar.append(this.select_input.node) } getValue(): string { - return this.select_input.node.getAttribute('value'); + return this.select_input.node.getAttribute('value') } setValue(value: string) { - this.select_input.set(value); + this.select_input.set(value) } getDefault(): string { - return Object.keys(this.options.options)[0] ?? ''; + return Object.keys(this.options.options)[0] ?? '' } -}; +} FormElement.types.inline_select = class FormElementInlineSelect extends FormElement { build(bar: HTMLDivElement) { - super.build(bar); - let options = []; - let val = this.options.value || this.options.default; - let i = 0; + super.build(bar) + let options = [] + let val = this.options.value || this.options.default + let i = 0 for (let key in this.options.options) { - let is_selected = val ? key == val : i == 0; - let text: string = typeof this.options.options[key] == 'string' ? this.options.options[key] : this.options.options[key].name; - let node = Interface.createElement('li', {class: is_selected ? 'selected' : '', key: key}, tl(text)); + let is_selected = val ? key == val : i == 0 + let text: string = + typeof this.options.options[key] == 'string' + ? this.options.options[key] + : this.options.options[key].name + let node = Interface.createElement( + 'li', + { class: is_selected ? 'selected' : '', key: key }, + tl(text) + ) node.onclick = () => { options.forEach(li => { - li.classList.toggle('selected', li == node); + li.classList.toggle('selected', li == node) }) - this.change(); + this.change() } - options.push(node); - i++; + options.push(node) + i++ } - let wrapper = Interface.createElement('ul', {class: 'form_inline_select'}, options); - bar.append(wrapper); + let wrapper = Interface.createElement('ul', { class: 'form_inline_select' }, options) + bar.append(wrapper) } getValue(): string { - return $(this.bar).find('li.selected')[0]?.getAttribute('key') || ''; + return $(this.bar).find('li.selected')[0]?.getAttribute('key') || '' } setValue(value: string) { - $(this.bar).find('li').each((i, el) => { - el.classList.toggle('selected', el.getAttribute('key') == value); - }) + $(this.bar) + .find('li') + .each((i, el) => { + el.classList.toggle('selected', el.getAttribute('key') == value) + }) } getDefault(): string { - return Object.keys(this.options.options)[0] ?? ''; + return Object.keys(this.options.options)[0] ?? '' } -}; +} FormElement.types.inline_multi_select = class FormElementInlineMultiSelect extends FormElement { value: Record build(bar: HTMLDivElement) { - super.build(bar); - let val = this.options.value || this.options.default; - this.value = {}; + super.build(bar) + let val = this.options.value || this.options.default + this.value = {} if (val) { for (let key in this.options.options) { - this.value[key] = !!val[key]; + this.value[key] = !!val[key] } } - let i = 0; - let options = []; + let i = 0 + let options = [] for (let key in this.options.options) { - let is_selected = val && val[key]; - let text = this.options.options[key]; - if (typeof text != 'string') text = text.name; - let node = Interface.createElement('li', {class: is_selected ? 'selected' : '', key: key}, tl(text)); + let is_selected = val && val[key] + let text = this.options.options[key] + if (typeof text != 'string') text = text.name + let node = Interface.createElement( + 'li', + { class: is_selected ? 'selected' : '', key: key }, + tl(text) + ) node.onclick = () => { - this.value[key] = !this.value[key]; - node.classList.toggle('selected', this.value[key]); - this.change(); + this.value[key] = !this.value[key] + node.classList.toggle('selected', this.value[key]) + this.change() } - options.push(node); - i++; + options.push(node) + i++ } - let wrapper = Interface.createElement('ul', {class: 'form_inline_select multi_select'}, options); + let wrapper = Interface.createElement( + 'ul', + { class: 'form_inline_select multi_select' }, + options + ) bar.append(wrapper) } getValue(): Record { - return this.value; + return this.value } setValue(value: Record) { for (let key in value) { - if (this.value[key] !== undefined) { - this.value[key] = value[key]; + if (this.value[key] !== undefined) { + this.value[key] = value[key] } } - $(this.bar).find('li').each((i, el) => { - el.classList.toggle('selected', !!this.value[el.getAttribute('key')]); - }) + $(this.bar) + .find('li') + .each((i, el) => { + el.classList.toggle('selected', !!this.value[el.getAttribute('key')]) + }) } getDefault(): Record { - return {}; + return {} } -}; +} FormElement.types.radio = class FormElementRadio extends FormElement { input: HTMLInputElement build(bar: HTMLDivElement) { - super.build(bar); + super.build(bar) let el = $(`
`) for (let key in this.options.options) { - let name = tl(typeof this.options.options[key] == 'string' ? this.options.options[key] : this.options.options[key].name) + let name = tl( + typeof this.options.options[key] == 'string' + ? this.options.options[key] + : this.options.options[key].name + ) el.append(`
`) - this.input = el.find(`input#${key}`)[0] as HTMLInputElement; - this.input.addEventListener('change', (args) => { - this.change(); + this.input = el.find(`input#${key}`)[0] as HTMLInputElement + this.input.addEventListener('change', args => { + this.change() }) } - bar.append(el[0]); + bar.append(el[0]) } getValue(): string { - return $(this.bar).find('.form_part_radio#'+this.id+' input:checked').attr('id'); + return $(this.bar) + .find('.form_part_radio#' + this.id + ' input:checked') + .attr('id') } setValue(value: string) { - $(this.bar).find('.form_part_radio input#'+value).prop('checked', true); + $(this.bar) + .find('.form_part_radio input#' + value) + .prop('checked', true) } getDefault() { - return Object.keys(this.options.options)[0] ?? ''; + return Object.keys(this.options.options)[0] ?? '' } -}; +} FormElement.types.buttons = class FormElementButtons extends FormElement { get uses_wide_inputs(): boolean { - return true; + return true } build(bar: HTMLDivElement) { - this.bar = bar; - let list = document.createElement('div'); - list.className = 'dialog_form_buttons'; + this.bar = bar + let list = document.createElement('div') + list.className = 'dialog_form_buttons' this.options.buttons.forEach((button_text, index) => { - let button = document.createElement('a'); - button.innerText = tl(button_text); + let button = document.createElement('a') + button.innerText = tl(button_text) button.addEventListener('click', e => { - this.options.click(index); + this.options.click(index) }) - list.append(button); + list.append(button) }) - bar.append(list); + bar.append(list) } -}; +} FormElement.types.num_slider = class FormElementNumSlider extends FormElement { slider: NumSlider build(bar: HTMLDivElement) { - super.build(bar); - let getInterval = this.options.getInterval; + super.build(bar) + let getInterval = this.options.getInterval // @ts-ignore - if (this.options.interval_type == 'position') getInterval = getSpatialInterval; + if (this.options.interval_type == 'position') getInterval = getSpatialInterval // @ts-ignore - if (this.options.interval_type == 'rotation') getInterval = getRotationInterval; - this.slider = new NumSlider('form_slider_'+this.id, { + if (this.options.interval_type == 'rotation') getInterval = getRotationInterval + this.slider = new NumSlider('form_slider_' + this.id, { private: true, // @ts-ignore onChange: () => { - this.change(); + this.change() }, - color: this.options.color || "", + color: this.options.color || '', getInterval, settings: { default: this.options.value || 0, min: this.options.min, max: this.options.max, - step: this.options.step||1, + step: this.options.step || 1, }, - }); - bar.append(this.slider.node); - this.slider.update(); + }) + bar.append(this.slider.node) + this.slider.update() } getValue(): number { - return this.slider.get(); + return this.slider.get() } setValue(value: number) { - this.slider.setValue(value); + this.slider.setValue(value) } getDefault() { - return Math.clamp(0, this.options.min, this.options.max); + return Math.clamp(0, this.options.min, this.options.max) } -}; +} FormElement.types.vector = class FormElementVector extends FormElement { linked_ratio: boolean constructor(id: string, options: FormElementOptions, form: InputForm) { - super(id, options, form); - this.linked_ratio = false; + super(id, options, form) + this.linked_ratio = false } build(bar: HTMLDivElement) { - super.build(bar); - let scope = this; + super.build(bar) + let scope = this let group = $(`
`) bar.append(group[0]) - let vector_inputs = []; - let initial_value = this.options.value instanceof Array ? this.options.value.slice() : [1, 1, 1]; - let updateInputs = (changed_input) => { - let i2 = -1; + let vector_inputs = [] + let initial_value = + this.options.value instanceof Array ? this.options.value.slice() : [1, 1, 1] + let updateInputs = changed_input => { + let i2 = -1 for (let vector_input_2 of vector_inputs) { - i2++; - if (vector_input_2 == changed_input) continue; - let new_value = initial_value[i2] * (changed_input.value / initial_value[vector_inputs.indexOf(changed_input)]); + i2++ + if (vector_input_2 == changed_input) continue + let new_value = + initial_value[i2] * + (changed_input.value / initial_value[vector_inputs.indexOf(changed_input)]) new_value = Math.clamp(new_value, this.options.min, this.options.max) if (this.options.force_step && this.options.step) { - new_value = Math.round(new_value / this.options.step) * this.options.step; + new_value = Math.round(new_value / this.options.step) * this.options.step } - vector_input_2.value = new_value; + vector_input_2.value = new_value } } for (let i = 0; i < (this.options.dimensions || 3); i++) { let numeric_input = new Interface.CustomElements.NumericInput(this.id + '_' + i, { value: this.options.value ? this.options.value[i] : 0, - min: this.options.min, max: this.options.max, step: this.options.step, + min: this.options.min, + max: this.options.max, + step: this.options.step, onChange() { if (scope.linked_ratio) { - updateInputs(numeric_input); + updateInputs(numeric_input) } - scope.change(); - } - }); + scope.change() + }, + }) group.append(numeric_input.node) - vector_inputs.push(numeric_input); + vector_inputs.push(numeric_input) } if (typeof this.options.linked_ratio == 'boolean') { let updateState = () => { - icon.textContent = scope.linked_ratio ? 'link' : 'link_off'; - linked_ratio_toggle.classList.toggle('enabled', scope.linked_ratio); - }; - this.linked_ratio = this.options.linked_ratio; - let icon = Blockbench.getIconNode('link'); - let linked_ratio_toggle = Interface.createElement('div', {class: 'tool linked_ratio_toggle'}, icon); + icon.textContent = scope.linked_ratio ? 'link' : 'link_off' + linked_ratio_toggle.classList.toggle('enabled', scope.linked_ratio) + } + this.linked_ratio = this.options.linked_ratio + let icon = Blockbench.getIconNode('link') + let linked_ratio_toggle = Interface.createElement( + 'div', + { class: 'tool linked_ratio_toggle' }, + icon + ) linked_ratio_toggle.addEventListener('click', event => { - this.linked_ratio = !this.linked_ratio; + this.linked_ratio = !this.linked_ratio if (this.linked_ratio) { - initial_value = vector_inputs.map(v => v.value); + initial_value = vector_inputs.map(v => v.value) // updateInputs(vector_inputs[0]); // scope.change() } - updateState(); + updateState() }) - updateState(); + updateState() group.append(linked_ratio_toggle) } } getValue(): number[] { - let result: number[] = []; + let result: number[] = [] for (let i = 0; i < (this.options.dimensions || 3); i++) { - let input_value = $(this.bar).find(`input#${this.id}_${i}`).val() as string; - let num = Math.clamp(parseFloat(input_value)||0, this.options.min, this.options.max); + let input_value = $(this.bar).find(`input#${this.id}_${i}`).val() as string + let num = Math.clamp(parseFloat(input_value) || 0, this.options.min, this.options.max) if (this.options.force_step && this.options.step) { - num = Math.round(num / this.options.step) * this.options.step; - }; + num = Math.round(num / this.options.step) * this.options.step + } - result.push(num); + result.push(num) } - return result; + return result } setValue(value: number[]) { for (let i = 0; i < (this.options.dimensions || 3); i++) { - $(this.bar).find(`input#${this.id}_${i}`).val(value[i]); + $(this.bar).find(`input#${this.id}_${i}`).val(value[i]) } } getDefault(): number[] { - return new Array(this.options.dimensions??3).fill(Math.clamp(0, this.options.min, this.options.max)) + return new Array(this.options.dimensions ?? 3).fill( + Math.clamp(0, this.options.min, this.options.max) + ) } -}; +} FormElement.types.color = class FormElementColor extends FormElement { colorpicker: ColorPicker build(bar: HTMLDivElement) { - super.build(bar); - if (this.options.colorpicker) this.colorpicker = this.options.colorpicker; - let scope = this; + super.build(bar) + if (this.options.colorpicker) this.colorpicker = this.options.colorpicker + let scope = this if (!this.colorpicker) { this.colorpicker = new ColorPicker({ - id: 'cp_'+this.id, + id: 'cp_' + this.id, name: tl(this.options.label), // @ts-ignore label: false, private: true, - value: this.options.value + value: this.options.value, }) } // @ts-ignore - this.colorpicker.onChange = function() { - scope.change(); - }; + this.colorpicker.onChange = function () { + scope.change() + } this.colorpicker.on('modify_color', () => { - scope.change(); + scope.change() }) bar.append(this.colorpicker.getNode()) } getValue(): tinycolor.Instance { - return this.colorpicker.get(); + return this.colorpicker.get() } setValue(value: string | any) { - this.colorpicker.set(value); + this.colorpicker.set(value) } getDefault() { - return '#ffffff'; + return '#ffffff' } -}; +} FormElement.types.checkbox = class FormElementCheckbox extends FormElement { input: HTMLInputElement build(bar: HTMLDivElement) { - super.build(bar); + super.build(bar) this.input = Interface.createElement('input', { type: 'checkbox', class: 'focusable_input', id: this.id, }) - this.input.checked = this.options.value; + this.input.checked = this.options.value bar.append(this.input) this.input.addEventListener('change', () => { - this.change(); + this.change() }) } setValue(value: boolean) { - this.input.checked = value; + this.input.checked = value } getValue(): boolean { - return this.input.checked; + return this.input.checked } getDefault() { - return false; + return false } -}; +} class FormElementFile extends FormElement { file: Filesystem.FileResult @@ -931,100 +1024,121 @@ class FormElementFile extends FormElement { content: any input: HTMLInputElement build(bar: HTMLDivElement) { - super.build(bar); - if (this.options.type == 'folder' && !isApp) return; - this.value = this.options.value; - let scope = this; + super.build(bar) + if (this.options.type == 'folder' && !isApp) return + this.value = this.options.value + let scope = this - let input = $(``); - this.input = input[0] as HTMLInputElement; - this.input.value = settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value || ''; - let input_wrapper = $('
'); - input_wrapper.append(input); - bar.append(input_wrapper[0]); - bar.classList.add('form_bar_file'); + let input = $( + `` + ) + this.input = input[0] as HTMLInputElement + this.input.value = settings.streamer_mode.value + ? `[${tl('generic.redacted')}]` + : this.value || '' + let input_wrapper = $('
') + input_wrapper.append(input) + bar.append(input_wrapper[0]) + bar.classList.add('form_bar_file') switch (this.options.type) { - case 'file': input_wrapper.append('insert_drive_file'); break; - case 'folder': input_wrapper.append('folder'); break; - case 'save': input_wrapper.append('save'); break; + case 'file': + input_wrapper.append('insert_drive_file') + break + case 'folder': + input_wrapper.append('folder') + break + case 'save': + input_wrapper.append('save') + break } - let remove_button = $('
clear
'); - bar.append(remove_button[0]); + let remove_button = $( + '
clear
' + ) + bar.append(remove_button[0]) remove_button.on('click', e => { - e.stopPropagation(); - this.value = ''; - delete this.content; - delete this.file; - input.val(''); + e.stopPropagation() + this.value = '' + delete this.content + delete this.file + input.val('') }) input_wrapper.on('click', e => { - const fileCB = (files) => { - this.value = files[0].path; - this.content = files[0].content; - this.file = files[0]; - input.val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value); - scope.change(); + const fileCB = files => { + this.value = files[0].path + this.content = files[0].content + this.file = files[0] + input.val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value) + scope.change() } switch (this.options.type) { case 'file': - Blockbench.import({ - resource_id: this.options.resource_id, - extensions: this.options.extensions, - type: this.options.filetype, - startpath: this.value, - readtype: this.options.readtype - }, fileCB); - break; + Blockbench.import( + { + resource_id: this.options.resource_id, + extensions: this.options.extensions, + type: this.options.filetype, + startpath: this.value, + readtype: this.options.readtype, + }, + fileCB + ) + break case 'folder': let path = Blockbench.pickDirectory({ startpath: this.value, }) - if (path) fileCB([{path}]); - break; + if (path) fileCB([{ path }]) + break case 'save': - Blockbench.export({ - resource_id: this.options.resource_id, - extensions: this.options.extensions, - type: this.options.filetype, - startpath: this.value, - custom_writer: () => {}, - }, path => { - this.value = path; - input.val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value); - scope.change(); - }); - break; + Blockbench.export( + { + resource_id: this.options.resource_id, + extensions: this.options.extensions, + type: this.options.filetype, + startpath: this.value, + custom_writer: () => {}, + }, + path => { + this.value = path + input.val( + settings.streamer_mode.value + ? `[${tl('generic.redacted')}]` + : this.value + ) + scope.change() + } + ) + break } }) } getValue(): Filesystem.FileResult | string | any { if (this.options.return_as == 'file') { - return this.file; + return this.file } else { - return isApp ? this.value : this.content; + return isApp ? this.value : this.content } } setValue(value: string) { - delete this.file; + delete this.file if (this.options.return_as == 'file' && typeof value == 'object') { - this.file = value; - this.value = this.file.name; + this.file = value + this.value = this.file.name } else if (isApp) { - this.value = value; + this.value = value } else { - this.content = value; + this.content = value } - $(this.input).val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value); + $(this.input).val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value) } getDefault() { - return ''; + return '' } -}; -FormElement.types.file = FormElementFile; -FormElement.types.folder = FormElementFile; -FormElement.types.save = FormElementFile; - +} +FormElement.types.file = FormElementFile +FormElement.types.folder = FormElementFile +FormElement.types.save = FormElementFile -Object.assign(window, {InputForm, FormElement}); +Object.assign(window, { InputForm, FormElement }) diff --git a/js/interface/panels.ts b/js/interface/panels.ts index dd6b9cbf7..ce5f7c32b 100644 --- a/js/interface/panels.ts +++ b/js/interface/panels.ts @@ -1,10 +1,16 @@ -import { Prop } from "../misc"; -import { EventSystem } from "../util/event_system"; -import { InputForm } from "./form"; -import { Interface, Panels, openTouchKeyboardModifierMenu, resizeWindow, updateInterface } from "./interface"; -import {Toolbar} from './toolbars' -import { Vue } from "../lib/libs"; -import { Blockbench } from "../api"; +import { Prop } from '../misc' +import { EventSystem } from '../util/event_system' +import { InputForm } from './form' +import { + Interface, + Panels, + openTouchKeyboardModifierMenu, + resizeWindow, + updateInterface, +} from './interface' +import { Toolbar } from './toolbars' +import { Vue } from '../lib/libs' +import { Blockbench } from '../api' interface PanelPositionData { slot: PanelSlot @@ -47,9 +53,7 @@ interface PanelOptions { [id: string]: Toolbar } | Toolbar[] - default_position?: - | Partial - | number + default_position?: Partial | number component?: Vue.Component form?: InputForm default_side?: 'right' | 'left' @@ -74,7 +78,7 @@ const DEFAULT_POSITION_DATA: PanelPositionData = { folded: false, fixed_height: false, attached_to: '', - attached_index: undefined + attached_index: undefined, } export class Panel extends EventSystem { @@ -112,910 +116,1124 @@ export class Panel extends EventSystem { resize_handles?: HTMLElement constructor(id: string | PanelOptions, data: PanelOptions) { - super(); - if (!data && typeof id != 'string') data = id; - let scope = this; - this.type = 'panel'; - this.id = typeof id == 'string' ? id : data.id || 'new_panel'; - this.name = tl(data.name ? data.name : `panel.${this.id}`); - this.icon = data.icon; - this.menu = data.menu; - this.condition = data.condition; - this.display_condition = data.display_condition; - this.previous_slot = 'left_bar'; - this.optional = data.optional ?? true; + super() + if (!data && typeof id != 'string') data = id + let scope = this + this.type = 'panel' + this.id = typeof id == 'string' ? id : data.id || 'new_panel' + this.name = tl(data.name ? data.name : `panel.${this.id}`) + this.icon = data.icon + this.menu = data.menu + this.condition = data.condition + this.display_condition = data.display_condition + this.previous_slot = 'left_bar' + this.optional = data.optional ?? true // @ts-ignore "Plugins" is loaded after so it cannot be imported - this.plugin = data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : ''); - - this.growable = data.growable; - this.resizable = data.resizable; - this.min_height = data.min_height ?? 60; - - this.onResize = data.onResize; - this.onFold = data.onFold; - this.events = {}; - this.toolbars = []; - this.open_attached_panel = this; - - if (!Interface.data.panels[this.id]) Interface.data.panels[this.id] = {}; - if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {}; - this.default_position = data.default_position || ({} as any); - if (this.default_position && this.default_position.slot) this.previous_slot = this.default_position.slot; - this.updatePositionData({slot: (data.default_side ? (data.default_side+'_bar') : 'left_bar') as PanelSlot}); + this.plugin = + data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : '') + + this.growable = data.growable + this.resizable = data.resizable + this.min_height = data.min_height ?? 60 + + this.onResize = data.onResize + this.onFold = data.onFold + this.events = {} + this.toolbars = [] + this.open_attached_panel = this + + if (!Interface.data.panels[this.id]) Interface.data.panels[this.id] = {} + if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {} + this.default_position = data.default_position || ({} as any) + if (this.default_position && this.default_position.slot) + this.previous_slot = this.default_position.slot + this.updatePositionData({ + slot: (data.default_side ? data.default_side + '_bar' : 'left_bar') as PanelSlot, + }) for (let mode_id in Interface.data.modes) { - let mode_data = Interface.getModeData(mode_id); - if (!mode_data.panels[this.id]) mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)); + let mode_data = Interface.getModeData(mode_id) + if (!mode_data.panels[this.id]) + mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)) } - this.handle = Interface.createElement('div', {class: 'panel_handle', panel_id: this.id}, Interface.createElement('span', {}, this.name)); - this.node = Interface.createElement('div', {class: 'panel', id: `panel_${this.id}`}); - this.tab_bar = Interface.createElement('div', {class: 'panel_tab_bar'}, [ - Interface.createElement('div', {class: 'panel_tab_list'}, this.handle) - ]); - this.container = Interface.createElement('div', {class: 'panel_container', panel_id: this.id}, [this.tab_bar, this.node]); + this.handle = Interface.createElement( + 'div', + { class: 'panel_handle', panel_id: this.id }, + Interface.createElement('span', {}, this.name) + ) + this.node = Interface.createElement('div', { class: 'panel', id: `panel_${this.id}` }) + this.tab_bar = Interface.createElement('div', { class: 'panel_tab_bar' }, [ + Interface.createElement('div', { class: 'panel_tab_list' }, this.handle), + ]) + this.container = Interface.createElement( + 'div', + { class: 'panel_container', panel_id: this.id }, + [this.tab_bar, this.node] + ) this.handle.addEventListener('mousedown', (event: MouseEvent) => { if (this.attached_to) { - Panels[this.attached_to].selectTab(this); + Panels[this.attached_to].selectTab(this) } else { - this.selectTab(); + this.selectTab() } - }); + }) if (this.growable) { - this.container.classList.add('grow'); - this.node.classList.add('grow'); + this.container.classList.add('grow') + this.node.classList.add('grow') } - + // Toolbars - let toolbars = data.toolbars instanceof Array ? data.toolbars : (data.toolbars ? Object.keys(data.toolbars) : []); + let toolbars = + data.toolbars instanceof Array + ? data.toolbars + : data.toolbars + ? Object.keys(data.toolbars) + : [] for (let item of toolbars) { - let toolbar = item instanceof Toolbar ? item : this.toolbars[item]; - if (toolbar instanceof Toolbar == false) continue; + let toolbar = item instanceof Toolbar ? item : this.toolbars[item] + if (toolbar instanceof Toolbar == false) continue if (toolbar.label) { - let label = Interface.createElement('p', {class: 'panel_toolbar_label'}, tl(toolbar.name)); - this.node.append(label); - toolbar.label_node = label; + let label = Interface.createElement( + 'p', + { class: 'panel_toolbar_label' }, + tl(toolbar.name) + ) + this.node.append(label) + toolbar.label_node = label } - this.node.append(toolbar.node); - this.toolbars.push(toolbar); + this.node.append(toolbar.node) + this.toolbars.push(toolbar) } if (data.form) { - this.form = data.form instanceof InputForm ? data.form : new InputForm(data.form); - this.node.append(this.form.node), - this.form.buildForm(); + this.form = data.form instanceof InputForm ? data.form : new InputForm(data.form) + ;(this.node.append(this.form.node), this.form.buildForm()) } if (data.component) { - - let component_mount = Interface.createElement('div'); - this.node.append(component_mount); - let onmounted = data.component.mounted; - data.component.mounted = function() { + let component_mount = Interface.createElement('div') + this.node.append(component_mount) + let onmounted = data.component.mounted + data.component.mounted = function () { Vue.nextTick(() => { - - let toolbar_wrappers = this.$el.querySelectorAll('.toolbar_wrapper'); + let toolbar_wrappers = this.$el.querySelectorAll('.toolbar_wrapper') toolbar_wrappers.forEach((wrapper: HTMLElement) => { - let id = wrapper.getAttribute('toolbar'); - let toolbar = scope.toolbars.find(toolbar => toolbar.id == id); + let id = wrapper.getAttribute('toolbar') + let toolbar = scope.toolbars.find(toolbar => toolbar.id == id) if (toolbar) { - wrapper.append(toolbar.node); + wrapper.append(toolbar.node) } }) if (typeof onmounted == 'function') { - onmounted.call(this); + onmounted.call(this) } //updateInterfacePanels() }) } this.vue = this.inside_vue = new Vue(data.component) - this.vue.$mount(component_mount); - this.vue.$el.classList.add('panel_vue_wrapper'); + this.vue.$mount(component_mount) + this.vue.$el.classList.add('panel_vue_wrapper') } if (!Blockbench.isMobile) { if (data.expand_button) { - let expand_button = Interface.createElement('div', {class: 'tool panel_control panel_expanding_button'}, Blockbench.getIconNode('fullscreen')) - this.tab_bar.append(expand_button); - expand_button.addEventListener('click', (e) => { + let expand_button = Interface.createElement( + 'div', + { class: 'tool panel_control panel_expanding_button' }, + Blockbench.getIconNode('fullscreen') + ) + this.tab_bar.append(expand_button) + expand_button.addEventListener('click', e => { if (this.slot == 'float') { - this.moveTo(this.previous_slot); + this.moveTo(this.previous_slot) } else { - this.moveTo('float'); - this.moveToFront(); + this.moveTo('float') + this.moveToFront() } }) } - let menu_button = Interface.createElement('div', {class: 'light_on_hover panel_menu_button'}, Blockbench.getIconNode('more_vert')) - this.handle.append(menu_button); - menu_button.addEventListener('click', (e) => { - this.snap_menu.open(menu_button, this); + let menu_button = Interface.createElement( + 'div', + { class: 'light_on_hover panel_menu_button' }, + Blockbench.getIconNode('more_vert') + ) + this.handle.append(menu_button) + menu_button.addEventListener('click', e => { + this.snap_menu.open(menu_button, this) }) - let fold_button = Interface.createElement('div', {class: 'tool panel_control panel_folding_button'}, Blockbench.getIconNode('expand_more')) - this.tab_bar.append(fold_button); - fold_button.addEventListener('click', (e) => { - this.fold(); + let fold_button = Interface.createElement( + 'div', + { class: 'tool panel_control panel_folding_button' }, + Blockbench.getIconNode('expand_more') + ) + this.tab_bar.append(fold_button) + fold_button.addEventListener('click', e => { + this.fold() }) this.tab_bar.firstElementChild.addEventListener('dblclick', e => { - this.fold(); + this.fold() }) if (this.resizable) { - this.sidebar_resize_handle = Interface.createElement('div', {class: 'panel_sidebar_resize_handle'}) - this.container.append(this.sidebar_resize_handle); + this.sidebar_resize_handle = Interface.createElement('div', { + class: 'panel_sidebar_resize_handle', + }) + this.container.append(this.sidebar_resize_handle) let resize = (e1: MouseEvent | TouchEvent) => { - e1 = convertTouchEvent(e1); - let height_before = this.container.clientHeight; - let started = false; - let direction = this.container.classList.contains('bottommost_panel') ? -1 : 1; - let other_panel_height_before = {}; + e1 = convertTouchEvent(e1) + let height_before = this.container.clientHeight + let started = false + let direction = this.container.classList.contains('bottommost_panel') ? -1 : 1 + let other_panel_height_before = {} - let other_panels = this.slot == 'right_bar' ? Interface.getRightPanels() : Interface.getLeftPanels(); + let other_panels = + this.slot == 'right_bar' + ? Interface.getRightPanels() + : Interface.getLeftPanels() - e1.preventDefault(); + e1.preventDefault() let drag = (e2: MouseEvent | TouchEvent) => { - e2 = convertTouchEvent(e2); - if (!started && (Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2)) > 12) { - started = true; - this.sidebar_resize_handle.classList.add('dragging'); + e2 = convertTouchEvent(e2) + if ( + !started && + Math.pow(e2.clientX - e1.clientX, 2) + + Math.pow(e2.clientY - e1.clientY, 2) > + 12 + ) { + started = true + this.sidebar_resize_handle.classList.add('dragging') } - if (!started) return; + if (!started) return - let change_amount = (e2.clientY - e1.clientY) * direction; - let sidebar_gap = this.container.parentElement.clientHeight; + let change_amount = (e2.clientY - e1.clientY) * direction + let sidebar_gap = this.container.parentElement.clientHeight for (let panel of other_panels) { - sidebar_gap -= panel.container.clientHeight; + sidebar_gap -= panel.container.clientHeight } - let height1 = this.position_data.height; - this.position_data.fixed_height = true; - this.position_data.height = Math.max(height_before + change_amount, this.min_height); - this.update(); - let height_difference = this.position_data.height - height1; - - let panel_b = other_panels.find(p => p != this && p.resizable && p.min_height < (p.height??p.container.clientHeight)); + let height1 = this.position_data.height + this.position_data.fixed_height = true + this.position_data.height = Math.max( + height_before + change_amount, + this.min_height + ) + this.update() + let height_difference = this.position_data.height - height1 + + let panel_b = other_panels.find( + p => + p != this && + p.resizable && + p.min_height < (p.height ?? p.container.clientHeight) + ) if (sidebar_gap < 1 && panel_b && change_amount > 0) { - if (!other_panel_height_before[panel_b.id]) other_panel_height_before[panel_b.id] = (panel_b.height??panel_b.container.clientHeight); - panel_b.position_data.fixed_height = true; - panel_b.position_data.height = Math.max(panel_b.position_data.height - height_difference, this.min_height); - panel_b.update(); + if (!other_panel_height_before[panel_b.id]) + other_panel_height_before[panel_b.id] = + panel_b.height ?? panel_b.container.clientHeight + panel_b.position_data.fixed_height = true + panel_b.position_data.height = Math.max( + panel_b.position_data.height - height_difference, + this.min_height + ) + panel_b.update() } } let stop = e2 => { - convertTouchEvent(e2); - - removeEventListeners(document, 'mousemove touchmove', drag); - removeEventListeners(document, 'mouseup touchend', stop); - this.sidebar_resize_handle.classList.remove('dragging'); + convertTouchEvent(e2) + + removeEventListeners(document, 'mousemove touchmove', drag) + removeEventListeners(document, 'mouseup touchend', stop) + this.sidebar_resize_handle.classList.remove('dragging') } - addEventListeners(document, 'mousemove touchmove', drag); - addEventListeners(document, 'mouseup touchend', stop); + addEventListeners(document, 'mousemove touchmove', drag) + addEventListeners(document, 'mouseup touchend', stop) } - addEventListeners(this.sidebar_resize_handle, 'mousedown touchstart', (event) => resize(event as MouseEvent)); + addEventListeners(this.sidebar_resize_handle, 'mousedown touchstart', event => + resize(event as MouseEvent) + ) } - let getHostPanelUnderCursor: (event: MouseEvent) => Panel | undefined = (event) => { + let getHostPanelUnderCursor: (event: MouseEvent) => Panel | undefined = event => { for (let panel_id in Panels) { - let panel: Panel = Panels[panel_id]; + let panel: Panel = Panels[panel_id] if (panel != this && panel.container.isConnected) { - let bounding_box = panel.tab_bar.getBoundingClientRect(); + let bounding_box = panel.tab_bar.getBoundingClientRect() if ( Math.isBetween(event.clientX, bounding_box.left, bounding_box.right) && Math.isBetween(event.clientY, bounding_box.top, bounding_box.bottom) ) { - return panel; + return panel } } } } addEventListeners(this.handle, 'mousedown touchstart', (e1: MouseEvent) => { - if (e1.target instanceof HTMLElement && e1.target.classList.contains('panel_menu_button')) return; - if (e1.which == 2 || e1.which == 3) return; - convertTouchEvent(e1); - let started = false; - let position_before = this.slot == 'float' - ? this.position_data.float_position.slice() - : [e1.clientX - e1.offsetX, e1.clientY - e1.offsetY - 55]; - let original_show_left_bar = Prop.show_left_bar; - let original_show_right_bar = Prop.show_right_bar; - - let target_slot: PanelSlot | undefined; - let target_panel: Panel | null; - let target_before = false; - let attach_to = false; - let move_attached_panels = e1.shiftKey || Pressing.overrides.shift; + if ( + e1.target instanceof HTMLElement && + e1.target.classList.contains('panel_menu_button') + ) + return + if (e1.which == 2 || e1.which == 3) return + convertTouchEvent(e1) + let started = false + let position_before = + this.slot == 'float' + ? this.position_data.float_position.slice() + : [e1.clientX - e1.offsetX, e1.clientY - e1.offsetY - 55] + let original_show_left_bar = Prop.show_left_bar + let original_show_right_bar = Prop.show_right_bar + + let target_slot: PanelSlot | undefined + let target_panel: Panel | null + let target_before = false + let attach_to = false + let move_attached_panels = e1.shiftKey || Pressing.overrides.shift function updateTargetHighlight(event: MouseEvent) { - $(`.panel_container[order], .panel_handle[order]`).attr('order', null); - $(`.panel_container.attach_target`).removeClass('attach_target'); + $(`.panel_container[order], .panel_handle[order]`).attr('order', null) + $(`.panel_container.attach_target`).removeClass('attach_target') if (attach_to && target_panel) { - target_panel.container.classList.add('attach_target'); - let panel_container_offset = $(target_panel.container).offset()?.left ?? 0; - let attached_panels = [target_panel].concat(target_panel.getAttachedPanels()); - let target_handle_panel = attached_panels.findLast(handle_panel => { - return event.clientX + 20 > panel_container_offset + handle_panel.handle.offsetLeft + handle_panel.handle.clientWidth; - }) ?? attached_panels[0]; + target_panel.container.classList.add('attach_target') + let panel_container_offset = $(target_panel.container).offset()?.left ?? 0 + let attached_panels = [target_panel].concat( + target_panel.getAttachedPanels() + ) + let target_handle_panel = + attached_panels.findLast(handle_panel => { + return ( + event.clientX + 20 > + panel_container_offset + + handle_panel.handle.offsetLeft + + handle_panel.handle.clientWidth + ) + }) ?? attached_panels[0] if (target_handle_panel) { - target_handle_panel.handle.setAttribute('order', '1'); + target_handle_panel.handle.setAttribute('order', '1') } } else if (target_panel) { - target_panel.container.setAttribute('order', (target_before ? -1 : 1).toString()); + target_panel.container.setAttribute( + 'order', + (target_before ? -1 : 1).toString() + ) } if (target_slot) { - Interface.center_screen.setAttribute('snapside', target_slot); + Interface.center_screen.setAttribute('snapside', target_slot) } else { - Interface.center_screen.removeAttribute('snapside'); + Interface.center_screen.removeAttribute('snapside') } - if ((target_slot == 'right_bar' && Interface.right_bar_width) || (target_slot == 'left_bar' && Interface.left_bar_width)) { - Interface.center_screen.removeAttribute('snapside'); + if ( + (target_slot == 'right_bar' && Interface.right_bar_width) || + (target_slot == 'left_bar' && Interface.left_bar_width) + ) { + Interface.center_screen.removeAttribute('snapside') } - Interface.left_bar.classList.toggle('drop_target', target_slot == 'left_bar'); - Interface.right_bar.classList.toggle('drop_target', target_slot == 'right_bar'); - - if (target_slot == 'left_bar' && !Prop.show_left_bar) Interface.toggleSidebar('left'); - if (target_slot == 'right_bar' && !Prop.show_right_bar) Interface.toggleSidebar('right'); - if (target_slot != 'left_bar' && Prop.show_left_bar && !original_show_left_bar) Interface.toggleSidebar('left'); - if (target_slot != 'right_bar' && Prop.show_right_bar && !original_show_right_bar) Interface.toggleSidebar('right'); + Interface.left_bar.classList.toggle('drop_target', target_slot == 'left_bar') + Interface.right_bar.classList.toggle('drop_target', target_slot == 'right_bar') + + if (target_slot == 'left_bar' && !Prop.show_left_bar) + Interface.toggleSidebar('left') + if (target_slot == 'right_bar' && !Prop.show_right_bar) + Interface.toggleSidebar('right') + if (target_slot != 'left_bar' && Prop.show_left_bar && !original_show_left_bar) + Interface.toggleSidebar('left') + if ( + target_slot != 'right_bar' && + Prop.show_right_bar && + !original_show_right_bar + ) + Interface.toggleSidebar('right') } let drag = e2 => { - convertTouchEvent(e2); - if (!started && (Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2)) > 15) { - started = true; - let attached_panels = this.getAttachedPanels(); + convertTouchEvent(e2) + if ( + !started && + Math.pow(e2.clientX - e1.clientX, 2) + + Math.pow(e2.clientY - e1.clientY, 2) > + 15 + ) { + started = true + let attached_panels = this.getAttachedPanels() if (attached_panels.length && !move_attached_panels) { - let first = attached_panels.splice(0, 1)[0]; - first.moveTo(this.slot, this); + let first = attached_panels.splice(0, 1)[0] + first.moveTo(this.slot, this) for (let other of attached_panels) { - first.attachPanel(other); + first.attachPanel(other) } } if (this.slot !== 'float' || this.attached_to) { - this.moveTo('float'); - this.moveToFront(); + this.moveTo('float') + this.moveToFront() } - this.node.classList.add('dragging'); + this.node.classList.add('dragging') - Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.move_panel_without_docking'); + Interface.addSuggestedModifierKey( + 'ctrl', + 'modifier_actions.move_panel_without_docking' + ) } - if (!started) return; - - this.position_data.float_position[0] = position_before[0] + e2.clientX - e1.clientX; - this.position_data.float_position[1] = position_before[1] + e2.clientY - e1.clientY; + if (!started) return - let threshold = 40; - let threshold_y = 64; - let host_target_panel; - target_slot = null; target_panel = null; target_before = false; attach_to = false; + this.position_data.float_position[0] = + position_before[0] + e2.clientX - e1.clientX + this.position_data.float_position[1] = + position_before[1] + e2.clientY - e1.clientY - if (e2.ctrlOrCmd) { - } else if (host_target_panel = getHostPanelUnderCursor(e2)) { - target_panel = host_target_panel; - attach_to = true; - target_slot = undefined; + let threshold = 40 + let threshold_y = 64 + let host_target_panel + target_slot = null + target_panel = null + target_before = false + attach_to = false + if (e2.ctrlOrCmd) { + } else if ((host_target_panel = getHostPanelUnderCursor(e2))) { + target_panel = host_target_panel + attach_to = true + target_slot = undefined } else if (e2.clientX < Math.max(Interface.left_bar_width, threshold)) { - - target_slot = 'left_bar'; + target_slot = 'left_bar' for (let child of Interface.left_bar.childNodes) { - if (!child.clientHeight) continue; - let y = $(child).offset()?.top; - if (!y) continue; - target_panel = Panels[child.getAttribute('panel_id')]; - if (e2.clientY < (y + child.clientHeight / 2)) { - target_before = true; - break; + if (!child.clientHeight) continue + let y = $(child).offset()?.top + if (!y) continue + target_panel = Panels[child.getAttribute('panel_id')] + if (e2.clientY < y + child.clientHeight / 2) { + target_before = true + break } } - - } else if (e2.clientX > document.body.clientWidth - Math.max(Interface.right_bar_width, threshold)) { - - target_slot = 'right_bar'; + } else if ( + e2.clientX > + document.body.clientWidth - Math.max(Interface.right_bar_width, threshold) + ) { + target_slot = 'right_bar' for (let child of Interface.right_bar.childNodes) { - if (!child.clientHeight) continue; - let y = $(child).offset()?.top; - if (!y) continue; - target_panel = Panels[child.getAttribute('panel_id')]; - if (e2.clientY < (y + child.clientHeight / 2)) { - target_before = true; - break; + if (!child.clientHeight) continue + let y = $(child).offset()?.top + if (!y) continue + target_panel = Panels[child.getAttribute('panel_id')] + if (e2.clientY < y + child.clientHeight / 2) { + target_before = true + break } } - } else if ( - e2.clientY < (Interface.work_screen.offsetTop + 30 + threshold_y) && - e2.clientX > Interface.left_bar_width && e2.clientX < (Interface.work_screen.clientWidth - Interface.right_bar_width) + e2.clientY < Interface.work_screen.offsetTop + 30 + threshold_y && + e2.clientX > Interface.left_bar_width && + e2.clientX < Interface.work_screen.clientWidth - Interface.right_bar_width ) { target_slot = 'top' - } else if ( - e2.clientY > Interface.work_screen.offsetTop + Interface.work_screen.clientHeight - Interface.status_bar.vue.$el.clientHeight - threshold_y && - e2.clientX > Interface.left_bar_width && e2.clientX < (Interface.work_screen.clientWidth - Interface.right_bar_width) + e2.clientY > + Interface.work_screen.offsetTop + + Interface.work_screen.clientHeight - + Interface.status_bar.vue.$el.clientHeight - + threshold_y && + e2.clientX > Interface.left_bar_width && + e2.clientX < Interface.work_screen.clientWidth - Interface.right_bar_width ) { target_slot = 'bottom' - } - updateTargetHighlight(e2); - this.update(true); - this.dispatchEvent('drag', {event: e2, target_before, attach_to, target_panel, target_slot}); + updateTargetHighlight(e2) + this.update(true) + this.dispatchEvent('drag', { + event: e2, + target_before, + attach_to, + target_panel, + target_slot, + }) } let stop = e2 => { - convertTouchEvent(e2); - this.node.classList.remove('dragging'); - Interface.center_screen.removeAttribute('snapside'); - $(`.panel_container[order], .panel_handle[order]`).attr('order', null); - Interface.left_bar.classList.remove('drop_target'); - Interface.right_bar.classList.remove('drop_target'); - $(`.panel_container.attach_target`).removeClass('attach_target'); - - Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.move_panel_without_docking'); + convertTouchEvent(e2) + this.node.classList.remove('dragging') + Interface.center_screen.removeAttribute('snapside') + $(`.panel_container[order], .panel_handle[order]`).attr('order', null) + Interface.left_bar.classList.remove('drop_target') + Interface.right_bar.classList.remove('drop_target') + $(`.panel_container.attach_target`).removeClass('attach_target') + + Interface.removeSuggestedModifierKey( + 'ctrl', + 'modifier_actions.move_panel_without_docking' + ) if (attach_to) { - this.fixed_height = false; - target_panel.attachPanel(this); + this.fixed_height = false + target_panel.attachPanel(this) } else if (target_slot) { - this.fixed_height = false; + this.fixed_height = false this.moveTo(target_slot, target_panel, target_before) } if (this.slot != 'float') { - this.position_data.float_position[0] = position_before[0]; - this.position_data.float_position[1] = position_before[1]; + this.position_data.float_position[0] = position_before[0] + this.position_data.float_position[1] = position_before[1] } - this.update(); - updateInterface(); - - removeEventListeners(document, 'mousemove touchmove', drag); - removeEventListeners(document, 'mouseup touchend', stop); - } - addEventListeners(document, 'mousemove touchmove', drag); - addEventListeners(document, 'mouseup touchend', stop); + this.update() + updateInterface() + removeEventListeners(document, 'mousemove touchmove', drag) + removeEventListeners(document, 'mouseup touchend', stop) + } + addEventListeners(document, 'mousemove touchmove', drag) + addEventListeners(document, 'mouseup touchend', stop) }) - } else { - - let close_button = Interface.createElement('div', {class: 'tool panel_control'}, Blockbench.getIconNode('clear')) - this.tab_bar.append(close_button); - close_button.addEventListener('click', (e) => { - Interface.PanelSelectorVue.select(null); + } else { + let close_button = Interface.createElement( + 'div', + { class: 'tool panel_control' }, + Blockbench.getIconNode('clear') + ) + this.tab_bar.append(close_button) + close_button.addEventListener('click', e => { + Interface.PanelSelectorVue.select(null) }) - this.tab_bar.classList.add('single_tab'); - - - addEventListeners(this.handle.firstElementChild as HTMLElement, 'mousedown touchstart', (e1: MouseEvent) => { - convertTouchEvent(e1); - let started = false; - let height_before = this.position_data.height; - let max = Blockbench.isLandscape ? window.innerWidth - 50 : Interface.work_screen.clientHeight; - - let drag = e2 => { - convertTouchEvent(e2); - let diff = Blockbench.isLandscape ? e1.clientX - e2.clientX : e1.clientY - e2.clientY; - if (!started && Math.abs(diff) > 4) { - started = true; - if (this.folded) this.fold(); + this.tab_bar.classList.add('single_tab') + + addEventListeners( + this.handle.firstElementChild as HTMLElement, + 'mousedown touchstart', + (e1: MouseEvent) => { + convertTouchEvent(e1) + let started = false + let height_before = this.position_data.height + let max = Blockbench.isLandscape + ? window.innerWidth - 50 + : Interface.work_screen.clientHeight + + let drag = e2 => { + convertTouchEvent(e2) + let diff = Blockbench.isLandscape + ? e1.clientX - e2.clientX + : e1.clientY - e2.clientY + if (!started && Math.abs(diff) > 4) { + started = true + if (this.folded) this.fold() + } + if (!started) return + + let sign = + Blockbench.isLandscape && settings.mobile_panel_side.value == 'left' + ? -1 + : 1 + this.position_data.height = Math.clamp( + height_before + diff * sign, + 140, + max + ) + + this.update(true) + resizeWindow() } - if (!started) return; - - let sign = (Blockbench.isLandscape && settings.mobile_panel_side.value == 'left') ? -1 : 1; - this.position_data.height = Math.clamp(height_before + diff * sign, 140, max); - - this.update(true); - resizeWindow(); + let stop = e2 => { + convertTouchEvent(e2) - } - let stop = e2 => { - convertTouchEvent(e2); + this.update() - this.update(); - - removeEventListeners(document, 'mousemove touchmove', drag); - removeEventListeners(document, 'mouseup touchend', stop); + removeEventListeners(document, 'mousemove touchmove', drag) + removeEventListeners(document, 'mouseup touchend', stop) + } + addEventListeners(document, 'mousemove touchmove', drag) + addEventListeners(document, 'mouseup touchend', stop) } - addEventListeners(document, 'mousemove touchmove', drag); - addEventListeners(document, 'mouseup touchend', stop); - - }) + ) } this.node.addEventListener('mousedown', event => { - setActivePanel(this.id); - this.moveToFront(); + setActivePanel(this.id) + this.moveToFront() }) this.handle.addEventListener('mousedown', event => { - setActivePanel(this.id); - this.moveToFront(); + setActivePanel(this.id) + this.moveToFront() }) - + // Add to slot if (!Blockbench.isMobile && !this.attached_to) { - let reference_panel = Panels[data.insert_before || data.insert_after]; - this.moveTo(this.position_data.slot, reference_panel, reference_panel && !data.insert_after); + let reference_panel = Panels[data.insert_before || data.insert_after] + this.moveTo( + this.position_data.slot, + reference_panel, + reference_panel && !data.insert_after + ) } - if (this.folded) this.fold(true); + if (this.folded) this.fold(true) - Panels[this.id] = this; + Panels[this.id] = this } isVisible() { - return !this.folded && this.node.parentElement && this.node.parentElement.style.display !== 'none'; + return ( + !this.folded && + this.node.parentElement && + this.node.parentElement.style.display !== 'none' + ) } isInSidebar() { - return this.slot === 'left_bar' || this.slot === 'right_bar'; + return this.slot === 'left_bar' || this.slot === 'right_bar' } get position_data(): PanelPositionData { - return Interface.getModeData().panels[this.id]; + return Interface.getModeData().panels[this.id] } get slot() { - return this.position_data.slot; + return this.position_data.slot } get folded() { - return this.position_data.folded; + return this.position_data.folded } set folded(state) { - this.position_data.folded = !!state; + this.position_data.folded = !!state } get fixed_height() { - return this.position_data.fixed_height; + return this.position_data.fixed_height } set fixed_height(state) { - this.position_data.fixed_height = !!state; + this.position_data.fixed_height = !!state } get attached_to() { - let data = this.position_data.attached_to; - return data; + let data = this.position_data.attached_to + return data } set attached_to(id) { - this.position_data.attached_to = id; + this.position_data.attached_to = id } get attached_index() { - return this.position_data.attached_index; + return this.position_data.attached_index } set attached_index(id: number) { - this.position_data.attached_index = id; + this.position_data.attached_index = id } dispatchEvent(event_name: PanelEvent, data: any): void { - super.dispatchEvent(event_name, data); + super.dispatchEvent(event_name, data) } updatePositionData(data: Partial = {}) { - let position_data = this.position_data; + let position_data = this.position_data for (let key in DEFAULT_POSITION_DATA) { if (position_data[key] == undefined) { - position_data[key] = this.default_position[key] - ?? data[key] - ?? structuredClone(DEFAULT_POSITION_DATA[key]); + position_data[key] = + this.default_position[key] ?? + data[key] ?? + structuredClone(DEFAULT_POSITION_DATA[key]) } } } getAttachedPanels(): Panel[] { - let panels: Panel[] = []; + let panels: Panel[] = [] for (let id in Panels) { - let panel = Panels[id] as Panel; + let panel = Panels[id] as Panel if (panel.attached_to == this.id && Condition(panel) && panel != this) { - panels.push(panel); + panels.push(panel) } } - panels.sort((a, b) => b.attached_index - a.attached_index); - return panels; + panels.sort((a, b) => b.attached_index - a.attached_index) + return panels } /** * Get the host panel if this panel is attached to another panel */ - getHostPanel(): Panel|undefined { - return Panels[this.attached_to]; + getHostPanel(): Panel | undefined { + return Panels[this.attached_to] } /** * Get the panel that acts as the container for this panel. If the panel is not attached to another panel, returns itself */ getContainerPanel(): Panel { - return Panels[this.attached_to] || this; + return Panels[this.attached_to] || this } attachPanel(panel: Panel, index?: number) { - let old_host_panel = panel.getHostPanel(); - panel.attached_to = this.id; - if (index != undefined) panel.attached_index = index; + let old_host_panel = panel.getHostPanel() + panel.attached_to = this.id + if (index != undefined) panel.attached_index = index - this.update(); + this.update() if (old_host_panel) { - old_host_panel.update(); + old_host_panel.update() } updateInterfacePanels() } - selectTab(panel: Panel = this): this{ + selectTab(panel: Panel = this): this { if (this.open_attached_panel != panel) { - this.open_attached_panel = panel; - this.update(); + this.open_attached_panel = panel + this.update() } - return this; + return this } resetCustomLayout(): this { - if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {}; + if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {} - this.updatePositionData(); + this.updatePositionData() for (let mode_id in Interface.data.modes) { - let mode_data = Interface.getModeData(mode_id); - if (!mode_data.panels[this.id]) mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)); + let mode_data = Interface.getModeData(mode_id) + if (!mode_data.panels[this.id]) + mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)) } - this.moveTo(this.slot); - this.fold(this.folded); - return this; + this.moveTo(this.slot) + this.fold(this.folded) + return this } addToolbar(toolbar: Toolbar, position = this.toolbars.length): void { - let nodes = []; + let nodes = [] if (toolbar.label) { - let label = Interface.createElement('p', {class: 'panel_toolbar_label'}, tl(toolbar.name)); - nodes.push(label); - toolbar.label_node = label; + let label = Interface.createElement( + 'p', + { class: 'panel_toolbar_label' }, + tl(toolbar.name) + ) + nodes.push(label) + toolbar.label_node = label } - nodes.push(toolbar.node); + nodes.push(toolbar.node) if (position == 0) { this.node.prepend(...nodes) } else if (typeof position == 'string') { - let anchor = this.node.querySelector(`.toolbar[toolbar_id="${position}"]`); + let anchor = this.node.querySelector(`.toolbar[toolbar_id="${position}"]`) if (anchor) { - anchor.after(...nodes); + anchor.after(...nodes) } } else { - this.node.append(...nodes); + this.node.append(...nodes) } - this.toolbars.splice(position, 0, toolbar); + this.toolbars.splice(position, 0, toolbar) } fold(state = !this.folded): this { - this.folded = !!state; - let new_icon = Blockbench.getIconNode(state ? 'expand_less' : 'expand_more'); - $(this.tab_bar).find('> .panel_folding_button > .icon').replaceWith(new_icon); - this.container.classList.toggle('folded', state); + this.folded = !!state + let new_icon = Blockbench.getIconNode(state ? 'expand_less' : 'expand_more') + $(this.tab_bar).find('> .panel_folding_button > .icon').replaceWith(new_icon) + this.container.classList.toggle('folded', state) if (this.onFold) { - this.onFold(); + this.onFold() } if (this.slot == 'top' || this.slot == 'bottom') { - resizeWindow(); + resizeWindow() } - this.update(); - this.dispatchEvent('fold', {}); - return this; + this.update() + this.dispatchEvent('fold', {}) + return this } setupFloatHandles(): this { let sides = [ - Interface.createElement('div', {class: 'panel_resize_side resize_top'}), - Interface.createElement('div', {class: 'panel_resize_side resize_bottom'}), - Interface.createElement('div', {class: 'panel_resize_side resize_left'}), - Interface.createElement('div', {class: 'panel_resize_side resize_right'}), - ]; + Interface.createElement('div', { class: 'panel_resize_side resize_top' }), + Interface.createElement('div', { class: 'panel_resize_side resize_bottom' }), + Interface.createElement('div', { class: 'panel_resize_side resize_left' }), + Interface.createElement('div', { class: 'panel_resize_side resize_right' }), + ] let corners = [ - Interface.createElement('div', {class: 'panel_resize_corner resize_top_left'}), - Interface.createElement('div', {class: 'panel_resize_corner resize_top_right'}), - Interface.createElement('div', {class: 'panel_resize_corner resize_bottom_left'}), - Interface.createElement('div', {class: 'panel_resize_corner resize_bottom_right'}), - ]; + Interface.createElement('div', { class: 'panel_resize_corner resize_top_left' }), + Interface.createElement('div', { class: 'panel_resize_corner resize_top_right' }), + Interface.createElement('div', { class: 'panel_resize_corner resize_bottom_left' }), + Interface.createElement('div', { class: 'panel_resize_corner resize_bottom_right' }), + ] let resize = (e1, direction_x, direction_y) => { - let position_before = this.position_data.float_position.slice(); - let size_before = [this.width, this.height]; - let started = false; + let position_before = this.position_data.float_position.slice() + let size_before = [this.width, this.height] + let started = false let drag = e2 => { - convertTouchEvent(e2); - if (!started && (Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2)) > 12) { - started = true; + convertTouchEvent(e2) + if ( + !started && + Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2) > 12 + ) { + started = true } - if (!started) return; + if (!started) return - this.position_data.float_size[0] = size_before[0] + (e2.clientX - e1.clientX) * direction_x; - this.position_data.float_size[1] = size_before[1] + (e2.clientY - e1.clientY) * direction_y; + this.position_data.float_size[0] = + size_before[0] + (e2.clientX - e1.clientX) * direction_x + this.position_data.float_size[1] = + size_before[1] + (e2.clientY - e1.clientY) * direction_y - if (direction_x == -1) this.position_data.float_position[0] = position_before[0] - this.position_data.float_size[0] + size_before[0]; - if (direction_y == -1) this.position_data.float_position[1] = position_before[1] - this.position_data.float_size[1] + size_before[1]; + if (direction_x == -1) + this.position_data.float_position[0] = + position_before[0] - this.position_data.float_size[0] + size_before[0] + if (direction_y == -1) + this.position_data.float_position[1] = + position_before[1] - this.position_data.float_size[1] + size_before[1] - this.update(); + this.update() } let stop = e2 => { - convertTouchEvent(e2); - - removeEventListeners(document, 'mousemove touchmove', drag); - removeEventListeners(document, 'mouseup touchend', stop); + convertTouchEvent(e2) + + removeEventListeners(document, 'mousemove touchmove', drag) + removeEventListeners(document, 'mouseup touchend', stop) } - addEventListeners(document, 'mousemove touchmove', drag); - addEventListeners(document, 'mouseup touchend', stop); + addEventListeners(document, 'mousemove touchmove', drag) + addEventListeners(document, 'mouseup touchend', stop) } - addEventListeners(sides[0], 'mousedown touchstart', (event) => resize(event, 0, -1)); - addEventListeners(sides[1], 'mousedown touchstart', (event) => resize(event, 0, 1)); - addEventListeners(sides[2], 'mousedown touchstart', (event) => resize(event, -1, 0)); - addEventListeners(sides[3], 'mousedown touchstart', (event) => resize(event, 1, 0)); - addEventListeners(corners[0], 'mousedown touchstart', (event) => resize(event, -1, -1)); - addEventListeners(corners[1], 'mousedown touchstart', (event) => resize(event, 1, -1)); - addEventListeners(corners[2], 'mousedown touchstart', (event) => resize(event, -1, 1)); - addEventListeners(corners[3], 'mousedown touchstart', (event) => resize(event, 1, 1)); - - let handles = Interface.createElement('div', {class: 'panel_resize_handle_wrapper'}, [...sides, ...corners]); - this.container.append(handles); - this.resize_handles = handles; - return this; + addEventListeners(sides[0], 'mousedown touchstart', event => resize(event, 0, -1)) + addEventListeners(sides[1], 'mousedown touchstart', event => resize(event, 0, 1)) + addEventListeners(sides[2], 'mousedown touchstart', event => resize(event, -1, 0)) + addEventListeners(sides[3], 'mousedown touchstart', event => resize(event, 1, 0)) + addEventListeners(corners[0], 'mousedown touchstart', event => resize(event, -1, -1)) + addEventListeners(corners[1], 'mousedown touchstart', event => resize(event, 1, -1)) + addEventListeners(corners[2], 'mousedown touchstart', event => resize(event, -1, 1)) + addEventListeners(corners[3], 'mousedown touchstart', event => resize(event, 1, 1)) + + let handles = Interface.createElement('div', { class: 'panel_resize_handle_wrapper' }, [ + ...sides, + ...corners, + ]) + this.container.append(handles) + this.resize_handles = handles + return this } moveToFront(): this { if (this.slot == 'float' && Panel.floating_panel_z_order[0] !== this.id) { - Panel.floating_panel_z_order.remove(this.id); - Panel.floating_panel_z_order.splice(0, 0, this.id); - let zindex = 18; + Panel.floating_panel_z_order.remove(this.id) + Panel.floating_panel_z_order.splice(0, 0, this.id) + let zindex = 18 Panel.floating_panel_z_order.forEach(id => { - let panel = Panels[id]; - panel.container.style.zIndex = zindex; - panel.dispatchEvent('change_zindex', {zindex}); - zindex = Math.clamp(zindex-1, 14, 19); + let panel = Panels[id] + panel.container.style.zIndex = zindex + panel.dispatchEvent('change_zindex', { zindex }) + zindex = Math.clamp(zindex - 1, 14, 19) }) } - return this; + return this } moveTo(slot: PanelSlot, ref_panel?: Panel, before = false): this { - let position_data = this.position_data; + let position_data = this.position_data if (slot == undefined) { - slot = ref_panel.position_data.slot; + slot = ref_panel.position_data.slot } if (slot !== this.slot) { - this.previous_slot = this.slot; + this.previous_slot = this.slot } // Reset attachment - this.position_data.attached_to = ''; - this.position_data.attached_index = 0; - this.container.append(this.node); - - this.dispatchEvent('move_to', {slot, ref_panel, before, previous_slot: this.previous_slot}); + this.position_data.attached_to = '' + this.position_data.attached_index = 0 + this.container.append(this.node) + + this.dispatchEvent('move_to', { + slot, + ref_panel, + before, + previous_slot: this.previous_slot, + }) - this.node.classList.remove('floating'); + this.node.classList.remove('floating') if (slot == 'left_bar' || slot == 'right_bar') { - let change_panel_order = !!ref_panel; + let change_panel_order = !!ref_panel if (!ref_panel && Interface.getModeData()[slot].includes(this.id)) { - let panels = Interface.getModeData()[slot].filter(id => Panels[id] && Panels[id].slot == slot || id == this.id); - let index = panels.indexOf(this.id); + let panels = Interface.getModeData()[slot].filter( + id => (Panels[id] && Panels[id].slot == slot) || id == this.id + ) + let index = panels.indexOf(this.id) if (index == 0) { - ref_panel = Panels[panels[1]]; - before = true; + ref_panel = Panels[panels[1]] + before = true } else { - ref_panel = Panels[panels[index-1]]; - before = false; + ref_panel = Panels[panels[index - 1]] + before = false } } if (ref_panel instanceof Panel && ref_panel.slot == slot) { if (before) { - $(ref_panel.node).before(this.node); + $(ref_panel.node).before(this.node) } else { - $(ref_panel.node).after(this.node); + $(ref_panel.node).after(this.node) } if (change_panel_order) { - Interface.getModeData()[slot].remove(this.id); - Interface.getModeData()[slot].splice(Interface.getModeData()[slot].indexOf(ref_panel.id) + (before ? 0 : 1), 0, this.id); + Interface.getModeData()[slot].remove(this.id) + Interface.getModeData()[slot].splice( + Interface.getModeData()[slot].indexOf(ref_panel.id) + (before ? 0 : 1), + 0, + this.id + ) } } else { - document.getElementById(slot)!.append(this.container); - Interface.getModeData()[slot].safePush(this.id); + document.getElementById(slot)!.append(this.container) + Interface.getModeData()[slot].safePush(this.id) } - } else if (slot == 'top') { - let top_panel = Interface.getTopPanel(); - if (top_panel && top_panel !== this && !Condition.mutuallyExclusive(this.condition, top_panel.condition)) { - top_panel.moveTo(top_panel.previous_slot); + let top_panel = Interface.getTopPanel() + if ( + top_panel && + top_panel !== this && + !Condition.mutuallyExclusive(this.condition, top_panel.condition) + ) { + top_panel.moveTo(top_panel.previous_slot) } - document.getElementById('top_slot')!.append(this.container); - + document.getElementById('top_slot')!.append(this.container) } else if (slot == 'bottom') { - let bottom_panel = Interface.getBottomPanel(); - if (bottom_panel && bottom_panel !== this && !Condition.mutuallyExclusive(this.condition, bottom_panel.condition)) { - bottom_panel.moveTo(bottom_panel.previous_slot); + let bottom_panel = Interface.getBottomPanel() + if ( + bottom_panel && + bottom_panel !== this && + !Condition.mutuallyExclusive(this.condition, bottom_panel.condition) + ) { + bottom_panel.moveTo(bottom_panel.previous_slot) } - document.getElementById('bottom_slot')!.append(this.container); - + document.getElementById('bottom_slot')!.append(this.container) } else if (slot == 'float' && !Blockbench.isMobile) { - Interface.work_screen.append(this.container); - this.node.classList.add('floating'); - this.dispatchEvent('change_zindex', {zindex: 14}); + Interface.work_screen.append(this.container) + this.node.classList.add('floating') + this.dispatchEvent('change_zindex', { zindex: 14 }) if (!this.resize_handles) { - this.setupFloatHandles(); + this.setupFloatHandles() } } else if (slot == 'hidden' && !Blockbench.isMobile) { - this.node.remove(); + this.node.remove() } if (slot !== 'float') { - Panel.floating_panel_z_order.remove(this.id); - this.node.style.zIndex = ''; - this.dispatchEvent('change_zindex', {zindex: null}); + Panel.floating_panel_z_order.remove(this.id) + this.node.style.zIndex = '' + this.dispatchEvent('change_zindex', { zindex: null }) } - position_data.slot = slot; - - this.updateSlot(); + position_data.slot = slot + + this.updateSlot() if (Panels[this.id]) { - this.dispatchEvent('moved_to', {slot, ref_panel, before, previous_slot: this.previous_slot}); + this.dispatchEvent('moved_to', { + slot, + ref_panel, + before, + previous_slot: this.previous_slot, + }) } - return this; + return this } updateSlot(): this { - let slot = this.slot; + let slot = this.slot - this.container.classList.remove('floating'); + this.container.classList.remove('floating') if (slot == 'left_bar' || slot == 'right_bar') { - - document.getElementById(slot)!.append(this.container); - Interface.getModeData()[slot].safePush(this.id); - + document.getElementById(slot)!.append(this.container) + Interface.getModeData()[slot].safePush(this.id) } else if (slot == 'top') { - document.getElementById('top_slot')!.append(this.container); - + document.getElementById('top_slot')!.append(this.container) } else if (slot == 'bottom') { - document.getElementById('bottom_slot')!.append(this.container); - + document.getElementById('bottom_slot')!.append(this.container) } else if (slot == 'float' && !Blockbench.isMobile) { - Interface.work_screen.append(this.container); - this.container.classList.add('floating'); - this.dispatchEvent('change_zindex', {zindex: 14}); + Interface.work_screen.append(this.container) + this.container.classList.add('floating') + this.dispatchEvent('change_zindex', { zindex: 14 }) if (!this.resize_handles) { - this.setupFloatHandles(); + this.setupFloatHandles() } } else if (slot == 'hidden') { - this.container.remove(); + this.container.remove() } if (slot !== 'float') { - Panel.floating_panel_z_order.remove(this.id); - this.container.style.zIndex = ''; - this.dispatchEvent('change_zindex', {zindex: null}); + Panel.floating_panel_z_order.remove(this.id) + this.container.style.zIndex = '' + this.dispatchEvent('change_zindex', { zindex: null }) } if (this.folded != this.container.classList.contains('folded')) { - this.folded = !!this.folded; - let new_icon = Blockbench.getIconNode(this.folded ? 'expand_less' : 'expand_more'); - $(this.handle).find('> .panel_folding_button > .icon').replaceWith(new_icon); - this.container.classList.toggle('folded', this.folded); + this.folded = !!this.folded + let new_icon = Blockbench.getIconNode(this.folded ? 'expand_less' : 'expand_more') + $(this.handle).find('> .panel_folding_button > .icon').replaceWith(new_icon) + this.container.classList.toggle('folded', this.folded) if (this.onFold) { - this.onFold(); + this.onFold() } } - - this.update(); + + this.update() if (Panels[this.id]) { - TickUpdates.interface = true; + TickUpdates.interface = true } - return this; + return this } update(dragging: boolean = false) { - let show = BARS.condition(this.condition); + let show = BARS.condition(this.condition) if (!Blockbench.isMobile) { // Hide panel if its in host panel - if (this.getHostPanel() && Condition(this.getHostPanel().condition)) show = false; + if (this.getHostPanel() && Condition(this.getHostPanel().condition)) show = false } - let work_screen = document.querySelector('div#work_screen'); - let center_screen = document.querySelector('div#center'); - let slot = this.slot; - let is_sidebar = slot == 'left_bar' || slot == 'right_bar'; + let work_screen = document.querySelector('div#work_screen') + let center_screen = document.querySelector('div#center') + let slot = this.slot + let is_sidebar = slot == 'left_bar' || slot == 'right_bar' if (show) { - this.container.classList.remove('hidden'); - this.node.classList.remove('attached'); + this.container.classList.remove('hidden') + this.node.classList.remove('attached') if (slot == 'float') { if (!dragging && work_screen.clientWidth) { - this.position_data.float_position[0] = Math.clamp(this.position_data.float_position[0], 0, work_screen.clientWidth - this.width); - this.position_data.float_position[1] = Math.clamp(this.position_data.float_position[1], 0, work_screen.clientHeight - this.height); - this.position_data.float_size[0] = Math.clamp(this.position_data.float_size[0], 200, work_screen.clientWidth - this.position_data.float_position[0]); - this.position_data.float_size[1] = Math.clamp(this.position_data.float_size[1], 86, work_screen.clientHeight - this.position_data.float_position[1]); + this.position_data.float_position[0] = Math.clamp( + this.position_data.float_position[0], + 0, + work_screen.clientWidth - this.width + ) + this.position_data.float_position[1] = Math.clamp( + this.position_data.float_position[1], + 0, + work_screen.clientHeight - this.height + ) + this.position_data.float_size[0] = Math.clamp( + this.position_data.float_size[0], + 200, + work_screen.clientWidth - this.position_data.float_position[0] + ) + this.position_data.float_size[1] = Math.clamp( + this.position_data.float_size[1], + 86, + work_screen.clientHeight - this.position_data.float_position[1] + ) } - this.container.style.left = this.position_data.float_position[0] + 'px'; - this.container.style.top = this.position_data.float_position[1] + 'px'; - this.width = this.position_data.float_size[0]; - this.height = this.position_data.float_size[1]; - if (this.folded) this.height = this.tab_bar.clientHeight; - this.container.style.width = this.width + 'px'; - this.container.style.height = this.height + 'px'; - this.container.classList.remove('bottommost_panel'); - this.container.classList.remove('topmost_panel'); + this.container.style.left = this.position_data.float_position[0] + 'px' + this.container.style.top = this.position_data.float_position[1] + 'px' + this.width = this.position_data.float_size[0] + this.height = this.position_data.float_size[1] + if (this.folded) this.height = this.tab_bar.clientHeight + this.container.style.width = this.width + 'px' + this.container.style.height = this.height + 'px' + this.container.classList.remove('bottommost_panel') + this.container.classList.remove('topmost_panel') } else { - this.container.style.width = this.container.style.left = this.container.style.top = null; + this.container.style.width = + this.container.style.left = + this.container.style.top = + null } if (Blockbench.isMobile) { - this.width = this.container.clientWidth; + this.width = this.container.clientWidth } else if (slot == 'left_bar') { - this.width = Interface.left_bar_width; + this.width = Interface.left_bar_width } else if (slot == 'right_bar') { - this.width = Interface.right_bar_width; + this.width = Interface.right_bar_width } if (slot == 'top' || slot == 'bottom') { - if (Blockbench.isMobile && Blockbench.isLandscape) { - this.height = center_screen.clientHeight; - this.width = Math.clamp(this.position_data.height, 30, center_screen.clientWidth); - if (this.folded) this.width = 72; + this.height = center_screen.clientHeight + this.width = Math.clamp( + this.position_data.height, + 30, + center_screen.clientWidth + ) + if (this.folded) this.width = 72 } else { - let opposite_panel = slot == 'top' ? Interface.getBottomPanel() : Interface.getTopPanel(); - this.height = Math.clamp(this.position_data.height, 30, center_screen.clientHeight - (opposite_panel ? opposite_panel.height : 0)); - if (this.folded) this.height = this.tab_bar.clientHeight; - this.width = Interface.work_screen.clientWidth - Interface.left_bar_width - Interface.right_bar_width; + let opposite_panel = + slot == 'top' ? Interface.getBottomPanel() : Interface.getTopPanel() + this.height = Math.clamp( + this.position_data.height, + 30, + center_screen.clientHeight - (opposite_panel ? opposite_panel.height : 0) + ) + if (this.folded) this.height = this.tab_bar.clientHeight + this.width = + Interface.work_screen.clientWidth - + Interface.left_bar_width - + Interface.right_bar_width } - this.container.style.width = this.width + 'px'; - this.container.style.height = this.height + 'px'; + this.container.style.width = this.width + 'px' + this.container.style.height = this.height + 'px' } else if (is_sidebar) { if (this.fixed_height) { //let other_panels = slot == 'left_bar' ? Interface.getLeftPanels() : Interface.getRightPanels(); //let available_height = (slot == 'left_bar' ? Interface.left_bar : Interface.right_bar).clientHeight; //let min_height = other_panels.reduce((sum, panel) => (panel == this ? sum : (sum - panel.node.clientHeight)), available_height); - this.height = Math.clamp(this.position_data.height, 30, Interface.work_screen.clientHeight); - this.container.style.height = this.height + 'px'; - this.container.classList.add('fixed_height'); + this.height = Math.clamp( + this.position_data.height, + 30, + Interface.work_screen.clientHeight + ) + this.container.style.height = this.height + 'px' + this.container.classList.add('fixed_height') } else { - this.container.style.height = null; + this.container.style.height = null } } - if (!this.fixed_height) this.container.classList.remove('fixed_height'); + if (!this.fixed_height) this.container.classList.remove('fixed_height') if (this.sidebar_resize_handle) { - this.sidebar_resize_handle.style.display = (is_sidebar) ? 'block' : 'none'; + this.sidebar_resize_handle.style.display = is_sidebar ? 'block' : 'none' } - if ((slot == 'right_bar' && Interface.getRightPanels().last() == this) || (slot == 'left_bar' && Interface.getLeftPanels().last() == this)) { - this.node.parentElement?.childNodes.forEach((n: HTMLElement) => n.classList.remove('bottommost_panel')); - this.container.classList.add('bottommost_panel'); + if ( + (slot == 'right_bar' && Interface.getRightPanels().last() == this) || + (slot == 'left_bar' && Interface.getLeftPanels().last() == this) + ) { + this.node.parentElement?.childNodes.forEach((n: HTMLElement) => + n.classList.remove('bottommost_panel') + ) + this.container.classList.add('bottommost_panel') } - if ((slot == 'right_bar' && Interface.getRightPanels()[0] == this) || (slot == 'left_bar' && Interface.getLeftPanels()[0] == this)) { - this.node.parentElement?.childNodes.forEach((n: HTMLElement) => n.classList.remove('topmost_panel')); - this.container.classList.add('topmost_panel'); + if ( + (slot == 'right_bar' && Interface.getRightPanels()[0] == this) || + (slot == 'left_bar' && Interface.getLeftPanels()[0] == this) + ) { + this.node.parentElement?.childNodes.forEach((n: HTMLElement) => + n.classList.remove('topmost_panel') + ) + this.container.classList.add('topmost_panel') } if (this.node.clientHeight) { - this.container.style.setProperty('--main-panel-height', this.node.clientHeight + 'px'); + this.container.style.setProperty( + '--main-panel-height', + this.node.clientHeight + 'px' + ) } if (Panels[this.id] && this.onResize) this.onResize() } else { - this.container.classList.add('hidden'); + this.container.classList.add('hidden') } if (!this.attached_to && !Blockbench.isMobile) { // This is a host panel. Update the tabs and attached panels - if (this.open_attached_panel && this.getAttachedPanels().includes(this.open_attached_panel) == false) { - this.open_attached_panel = this; + if ( + this.open_attached_panel && + this.getAttachedPanels().includes(this.open_attached_panel) == false + ) { + this.open_attached_panel = this } let tabs: Panel[] = [this] - tabs.safePush(...this.getAttachedPanels()); - $(this.tab_bar.firstElementChild).empty(); - let tab_amount = 0; + tabs.safePush(...this.getAttachedPanels()) + $(this.tab_bar.firstElementChild).empty() + let tab_amount = 0 for (let panel of tabs) { - this.tab_bar.firstElementChild.append(panel.handle); - panel.handle.classList.toggle('selected', this.open_attached_panel == panel); - tab_amount++; + this.tab_bar.firstElementChild.append(panel.handle) + panel.handle.classList.toggle('selected', this.open_attached_panel == panel) + tab_amount++ } - + if (this.id == 'uv') { this.id = 'uv' } - let panel_is_appended = false; + let panel_is_appended = false for (let panel_node of this.container.querySelectorAll('.panel')) { if (panel_node == this.open_attached_panel.node) { - panel_is_appended = true; + panel_is_appended = true } else { - panel_node.remove(); + panel_node.remove() } } - if (!panel_is_appended) this.container.append(this.open_attached_panel.node); - this.open_attached_panel.node.classList.add('attached'); - this.tab_bar.classList.toggle('single_tab', tab_amount <= 1); + if (!panel_is_appended) this.container.append(this.open_attached_panel.node) + this.open_attached_panel.node.classList.add('attached') + this.tab_bar.classList.toggle('single_tab', tab_amount <= 1) } - this.dispatchEvent('update', {show}); + this.dispatchEvent('update', { show }) localStorage.setItem('interface_data', JSON.stringify(Interface.data)) - return this; + return this } //Delete delete() { - delete Panels[this.id]; - this.node.remove(); - this.container.remove(); - updateInterfacePanels(); + delete Panels[this.id] + this.node.remove() + this.container.remove() + updateInterfacePanels() } static floating_panel_z_order: string[] = [] } @@ -1028,64 +1246,64 @@ Panel.prototype.snap_menu = new Menu([ name: 'menu.panel.move_to', icon: 'drag_handle', condition: () => !Blockbench.isMobile, - children: (panel: Panel) => ([ + children: (panel: Panel) => [ { name: 'menu.panel.move_to.left_bar', icon: 'align_horizontal_left', marked: panel => panel.slot == 'left_bar' && !panel.attached_to, - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('left_bar'); - } + click: panel => { + panel.fixed_height = false + panel.moveTo('left_bar') + }, }, { name: 'menu.panel.move_to.right_bar', icon: 'align_horizontal_right', marked: panel => panel.slot == 'right_bar' && !panel.attached_to, - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('right_bar'); - } + click: panel => { + panel.fixed_height = false + panel.moveTo('right_bar') + }, }, { name: 'menu.panel.move_to.top', icon: 'align_vertical_top', marked: panel => panel.slot == 'top' && !panel.attached_to, - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('top'); - } + click: panel => { + panel.fixed_height = false + panel.moveTo('top') + }, }, { name: 'menu.panel.move_to.bottom', icon: 'align_vertical_bottom', marked: panel => panel.slot == 'bottom' && !panel.attached_to, - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('bottom'); - } + click: panel => { + panel.fixed_height = false + panel.moveTo('bottom') + }, }, { name: 'menu.panel.move_to.float', icon: 'web_asset', marked: panel => panel.slot == 'float' && !panel.attached_to, - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('float'); - } + click: panel => { + panel.fixed_height = false + panel.moveTo('float') + }, }, '_', { name: 'menu.panel.move_to.hidden', icon: 'web_asset_off', marked: panel => panel.slot == 'hidden' && !panel.attached_to, - condition: panel => (panel.optional && panel.slot != 'hidden'), - click: (panel) => { - panel.fixed_height = false; - panel.moveTo('hidden'); - } - } - ]) + condition: panel => panel.optional && panel.slot != 'hidden', + click: panel => { + panel.fixed_height = false + panel.moveTo('hidden') + }, + }, + ], }, { id: 'move_to', @@ -1093,30 +1311,37 @@ Panel.prototype.snap_menu = new Menu([ icon: 'fa-diagram-next', condition: () => !Blockbench.isMobile, children: (panel: Panel) => { - let options: CustomMenuItem[] = []; + let options: CustomMenuItem[] = [] for (let id in Panels) { - let panel2: Panel = Panels[id]; - if (!Condition(panel2.condition) || panel2.attached_to || panel2.id == panel.attached_to || panel2 == panel) continue; + let panel2: Panel = Panels[id] + if ( + !Condition(panel2.condition) || + panel2.attached_to || + panel2.id == panel.attached_to || + panel2 == panel + ) + continue options.push({ id: panel2.id, name: panel2.name, icon: panel2.icon, click() { - panel2.attachPanel(panel); - } + panel2.attachPanel(panel) + }, }) } - return options; - } + return options + }, }, { id: 'fold', name: 'menu.panel.fold', icon: (panel: Panel) => panel.getContainerPanel().folded == true, - condition: (panel: Panel) => panel.getContainerPanel().slot != 'hidden' && !Blockbench.isMobile, + condition: (panel: Panel) => + panel.getContainerPanel().slot != 'hidden' && !Blockbench.isMobile, click(panel: Panel) { - panel.getContainerPanel().fold(); - } + panel.getContainerPanel().fold() + }, }, { id: 'enable', @@ -1124,57 +1349,59 @@ Panel.prototype.snap_menu = new Menu([ icon: (panel: Panel) => panel.slot != 'hidden', condition: (panel: Panel) => Blockbench.isMobile, click(panel: Panel) { - panel.fixed_height = false; + panel.fixed_height = false if (panel.slot == 'hidden') { - panel.moveTo('bottom'); + panel.moveTo('bottom') } else { - panel.moveTo('hidden'); + panel.moveTo('hidden') } - } - } + }, + }, ]) - export function setupPanels() { - Interface.panel_definers.forEach((definer) => { + Interface.panel_definers.forEach(definer => { if (typeof definer === 'function') { definer() } }) - updateSidebarOrder(); + updateSidebarOrder() } export function updateInterfacePanels() { - if (!Blockbench.isMobile) { - Interface.left_bar.style.display = Prop.show_left_bar ? 'flex' : 'none'; - Interface.right_bar.style.display = Prop.show_right_bar ? 'flex' : 'none'; + Interface.left_bar.style.display = Prop.show_left_bar ? 'flex' : 'none' + Interface.right_bar.style.display = Prop.show_right_bar ? 'flex' : 'none' } Interface.work_screen.style.setProperty( 'grid-template-columns', - Interface.left_bar_width+'px auto '+ Interface.right_bar_width +'px' + Interface.left_bar_width + 'px auto ' + Interface.right_bar_width + 'px' ) for (var key in Interface.Panels) { - var panel: Panel = Panels[key]; - panel.update(); + var panel: Panel = Panels[key] + panel.update() } - var left_width = Interface.left_bar.querySelector('.panel_container:not(.hidden)') ? Interface.left_bar_width : 0; - var right_width = Interface.right_bar.querySelector('.panel_container:not(.hidden)') ? Interface.right_bar_width : 0; + var left_width = Interface.left_bar.querySelector('.panel_container:not(.hidden)') + ? Interface.left_bar_width + : 0 + var right_width = Interface.right_bar.querySelector('.panel_container:not(.hidden)') + ? Interface.right_bar_width + : 0 if (!left_width || !right_width) { Interface.work_screen.style.setProperty( 'grid-template-columns', - left_width+'px auto '+ right_width +'px' + left_width + 'px auto ' + right_width + 'px' ) } - Interface.preview.style.visibility = Interface.preview.clientHeight > 80 ? 'visible' : 'hidden'; + Interface.preview.style.visibility = Interface.preview.clientHeight > 80 ? 'visible' : 'hidden' - let height = document.getElementById('center')!.clientHeight; - height -= Interface.getBottomPanel()?.height || 0; - height -= Interface.getTopPanel()?.height || 0; - Interface.preview.style.height = height > 0 ? (height + 'px') : ''; + let height = document.getElementById('center')!.clientHeight + height -= Interface.getBottomPanel()?.height || 0 + height -= Interface.getTopPanel()?.height || 0 + Interface.preview.style.height = height > 0 ? height + 'px' : '' if (Preview.split_screen.enabled) { Preview.split_screen.updateSize() @@ -1183,47 +1410,49 @@ export function updateInterfacePanels() { var resizer = Interface.Resizers[key] resizer.update() } - updateSidebarOrder(); + updateSidebarOrder() } export function updateSidebarOrder() { - ['left_bar', 'right_bar'].forEach(bar => { - let bar_node = document.querySelector(`.sidebar#${bar}`); - let current_panels = Array.from(bar_node.childNodes).map(panel_node => (panel_node as HTMLElement).getAttribute('panel_id')); + ;['left_bar', 'right_bar'].forEach(bar => { + let bar_node = document.querySelector(`.sidebar#${bar}`) + let current_panels = Array.from(bar_node.childNodes).map(panel_node => + (panel_node as HTMLElement).getAttribute('panel_id') + ) - let last_panel: Panel; - let panel_count = 0; + let last_panel: Panel + let panel_count = 0 Interface.getModeData()[bar].forEach((panel_id: string) => { - let panel: Panel = Panels[panel_id]; + let panel: Panel = Panels[panel_id] if (panel && panel.slot == bar) { - panel.container.classList.remove('bottommost_panel'); - panel.container.classList.remove('topmost_panel'); + panel.container.classList.remove('bottommost_panel') + panel.container.classList.remove('topmost_panel') if (!panel.attached_to && Condition(panel.condition)) { if (current_panels[panel_count] != panel_id) { - bar_node.append(panel.container); + bar_node.append(panel.container) } if (panel_count == 0) { - panel.container.classList.add('topmost_panel'); + panel.container.classList.add('topmost_panel') } - panel_count++; - last_panel = panel; + panel_count++ + last_panel = panel } else { - panel.container.remove(); + panel.container.remove() } } - }); + }) if (last_panel && panel_count > 1) { - last_panel.container.classList.add('bottommost_panel'); + last_panel.container.classList.add('bottommost_panel') } }) } export function updatePanelSelector() { - if (!Blockbench.isMobile) return; + if (!Blockbench.isMobile) return - Interface.PanelSelectorVue.$forceUpdate(); - let bottom_panel = Interface.getBottomPanel(); + Interface.PanelSelectorVue.$forceUpdate() + let bottom_panel = Interface.getBottomPanel() if (bottom_panel && !Condition(bottom_panel.display_condition)) { - Interface.PanelSelectorVue.select(null); + Interface.PanelSelectorVue.select(null) } } @@ -1237,41 +1466,44 @@ export function setupMobilePanelSelector() { data: { all_panels: Interface.Panels, selected: null, - modifiers: Pressing.overrides - }, - computed: { + modifiers: Pressing.overrides, }, + computed: {}, methods: { panels() { - let arr = []; + let arr = [] for (var id in this.all_panels) { - let panel = this.all_panels[id]; - if (Condition(panel.condition) && Condition(panel.display_condition) && panel.slot != 'hidden') { - arr.push(panel); + let panel = this.all_panels[id] + if ( + Condition(panel.condition) && + Condition(panel.display_condition) && + panel.slot != 'hidden' + ) { + arr.push(panel) } } - return arr; + return arr }, select(panel: Panel) { - this.selected = panel && panel.id; + this.selected = panel && panel.id for (let key in Panels) { - let panel_b = Panels[key]; + let panel_b = Panels[key] if (panel_b.slot == 'bottom') { - $(panel_b.container).detach(); - panel_b.position_data.slot = 'left_bar'; + $(panel_b.container).detach() + panel_b.position_data.slot = 'left_bar' } } if (panel) { - panel.moveTo('bottom'); + panel.moveTo('bottom') } else { - resizeWindow(); + resizeWindow() } }, openKeyboardMenu() { - openTouchKeyboardModifierMenu(this.$refs.mobile_keyboard_menu); + openTouchKeyboardModifierMenu(this.$refs.mobile_keyboard_menu) }, Condition, - getIconNode: Blockbench.getIconNode + getIconNode: Blockbench.getIconNode, }, template: `
@@ -1284,7 +1516,7 @@ export function setupMobilePanelSelector() {
keyboard
-
` +
`, }) } @@ -1296,4 +1528,4 @@ Object.assign(window, { updatePanelSelector, setActivePanel, setupMobilePanelSelector, -}); +}) diff --git a/js/interface/settings.ts b/js/interface/settings.ts index 12688a15b..e9d81b3da 100644 --- a/js/interface/settings.ts +++ b/js/interface/settings.ts @@ -1,13 +1,13 @@ -import { Vue } from "../lib/libs"; -import { Blockbench } from "../api"; -import { Dialog } from "./dialog"; -import { FormInputType } from "./form"; -import { ipcRenderer } from "../native_apis"; +import { Vue } from '../lib/libs' +import { Blockbench } from '../api' +import { Dialog } from './dialog' +import { FormInputType } from './form' +import { ipcRenderer } from '../native_apis' -export const settings: Record = {}; -export type settings_type = typeof settings; +export const settings: Record = {} +export type settings_type = typeof settings -type SettingsValue = string | number | boolean; +type SettingsValue = string | number | boolean enum SettingsType { Toggle = 'toggle', Number = 'number', @@ -66,187 +66,196 @@ export class Setting { keybind_label: string constructor(id: string, data: SettingOptions) { - this.id = id; - settings[id] = this; - this.type = SettingsType.Toggle; - if (data.type) this.type = data.type as SettingsType; + this.id = id + settings[id] = this + this.type = SettingsType.Toggle + if (data.type) this.type = data.type as SettingsType if (data.value != undefined) { - this.default_value = data.value; + this.default_value = data.value } else { switch (this.type) { - case 'toggle': this.default_value = true; break; - case 'number': this.default_value = 0; break; - case 'text': this.default_value = ''; break; - case 'password': this.default_value = ''; break; - case 'select': this.default_value; break; - case 'click': this.default_value = false; break; + case 'toggle': + this.default_value = true + break + case 'number': + this.default_value = 0 + break + case 'text': + this.default_value = '' + break + case 'password': + this.default_value = '' + break + case 'select': + this.default_value + break + case 'click': + this.default_value = false + break } } if (typeof Settings.stored[id] === 'object') { // @ts-ignore - this.master_value = Settings.stored[id].value; - + this.master_value = Settings.stored[id].value } else if (data.value != undefined) { this.master_value = data.value - } else { - this.master_value = this.default_value; - } - this.condition = data.condition; - this.category = data.category || 'general'; - this.name = data.name || tl(`settings.${id}`); - this.description = data.description || tl(`settings.${id}.desc`); - this.requires_restart = data.requires_restart == true; - this.launch_setting = data.launch_setting || false; + this.master_value = this.default_value + } + this.condition = data.condition + this.category = data.category || 'general' + this.name = data.name || tl(`settings.${id}`) + this.description = data.description || tl(`settings.${id}.desc`) + this.requires_restart = data.requires_restart == true + this.launch_setting = data.launch_setting || false // @ts-ignore plugin code is loaded after this, so "Plugins" cannot be imported here - this.plugin = data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : ''); + this.plugin = + data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : '') if (this.type == 'number') { - this.min = data.min; - this.max = data.max; - this.step = data.step; + this.min = data.min + this.max = data.max + this.step = data.step } if (this.type == 'click') { - this.icon = data.icon; - this.click = data.click; + this.icon = data.icon + this.click = data.click } if (this.type == 'select') { - this.options = data.options; + this.options = data.options } if (this.type == 'password') { - this.hidden = true; + this.hidden = true } if (typeof data.onChange == 'function') { this.onChange = data.onChange } //add to structure - var category = Settings.structure[this.category]; + var category = Settings.structure[this.category] if (category) { - category.items[id] = this; - let before = category.open; - category.open = false; + category.items[id] = this + let before = category.open + category.open = false Vue.nextTick(() => { - category.open = before; + category.open = before }) } if (!this.icon) { - if (this.type == 'toggle') this.icon = this.value ? 'check_box' : 'check_box_outline_blank'; - if (this.type == 'number') this.icon = 'tag'; - if (this.type == 'password') this.icon = 'password'; - if (this.type == 'text') this.icon = 'format_color_text'; - if (this.type == 'select') this.icon = 'list'; - if (!this.icon) this.icon = 'settings'; - } - this.keybind_label = tl('data.setting'); + if (this.type == 'toggle') + this.icon = this.value ? 'check_box' : 'check_box_outline_blank' + if (this.type == 'number') this.icon = 'tag' + if (this.type == 'password') this.icon = 'password' + if (this.type == 'text') this.icon = 'format_color_text' + if (this.type == 'select') this.icon = 'list' + if (!this.icon) this.icon = 'settings' + } + this.keybind_label = tl('data.setting') if (Blockbench.setup_successful) { - Settings.saveLocalStorages(); + Settings.saveLocalStorages() } } /** * The active value */ get value(): SettingsValue { - let profile = SettingsProfile.all.find(profile => profile.isActive() && profile.settings[this.id] !== undefined); + let profile = SettingsProfile.all.find( + profile => profile.isActive() && profile.settings[this.id] !== undefined + ) if (profile) { - return profile.settings[this.id] ?? this.master_value; + return profile.settings[this.id] ?? this.master_value } else { - return this.master_value; + return this.master_value } } set value(value: SettingsValue) { - this.master_value = value; + this.master_value = value } /** * The value that is displayed in the settings dialog */ get ui_value(): SettingsValue { - let profile = Settings.dialog.content_vue?.$data.profile; + let profile = Settings.dialog.content_vue?.$data.profile if (profile) { - return profile.settings[this.id] ?? this.master_value; + return profile.settings[this.id] ?? this.master_value } else { - return this.master_value; + return this.master_value } } set ui_value(value: SettingsValue) { - let profile = Settings.dialog.content_vue?.$data.profile; + let profile = Settings.dialog.content_vue?.$data.profile if (this.type == 'number') value = Math.clamp(value as number, this.min, this.max) if (profile) { - Vue.set(profile.settings, this.id, value); + Vue.set(profile.settings, this.id, value) } else { - this.master_value = value; + this.master_value = value } } delete() { if (settings[this.id]) { - delete settings[this.id]; + delete settings[this.id] } if (Settings.structure[this.category] && Settings.structure[this.category].items[this.id]) { - delete Settings.structure[this.category].items[this.id]; + delete Settings.structure[this.category].items[this.id] } } /** * Sets the value of the setting, while triggering the onChange function if available, and saving the change. */ set(value: SettingsValue) { - if (value === undefined || value === null) return; - let old_value = this.value; + if (value === undefined || value === null) return + let old_value = this.value if (this.type == 'number' && typeof value == 'number') { if (this.step) { - value = Math.round(value / this.step) * this.step; + value = Math.round(value / this.step) * this.step } this.value = Math.clamp(value, this.min, this.max) } else if (this.type == 'toggle') { - this.value = !!value; + this.value = !!value } else if (this.type == 'click') { - this.value = value; + this.value = value } else if (typeof value == 'string') { - this.value = value; + this.value = value } if (typeof this.onChange == 'function' && this.value !== old_value) { - this.onChange(this.value); + this.onChange(this.value) } - Settings.saveLocalStorages(); + Settings.saveLocalStorages() } /** * Triggers the setting, as if selected in action control. This toggles boolean settings, opens a dialog for string or numeric settings, etc. */ trigger(e: KeyboardEvent | MouseEvent) { - let {type} = this; - let setting = this; + let { type } = this + let setting = this if (type == 'toggle') { - this.set(!this.value); - Settings.save(); + this.set(!this.value) + Settings.save() if (setting.requires_restart) { - Settings.showRestartMessage(); + Settings.showRestartMessage() } - } else if (type == 'click') { this.click(e) - } else if (type == 'select') { - let list = []; + let list = [] for (let key in this.options) { list.push({ id: key, name: this.options[key], - icon: this.value == key - ? 'far.fa-dot-circle' - : 'far.fa-circle', + icon: this.value == key ? 'far.fa-dot-circle' : 'far.fa-circle', click: () => { - this.set(key); - Settings.save(); + this.set(key) + Settings.save() if (setting.requires_restart) { - Settings.showRestartMessage(); + Settings.showRestartMessage() } - } + }, }) } - new Menu(list).open(e.target as HTMLElement); - + new Menu(list).open(e.target as HTMLElement) } else { let input_types: Record = { click: 'checkbox', @@ -254,7 +263,7 @@ export class Setting { password: 'password', select: 'select', text: 'text', - toggle: 'checkbox' + toggle: 'checkbox', } let dialog = new Dialog({ id: 'setting_' + this.id, @@ -264,33 +273,34 @@ export class Setting { value: this.value, label: this.name, description: this.description, - type: input_types[this.type] as FormInputType + type: input_types[this.type] as FormInputType, }, - description: this.description ? { - type: 'buttons', - text: this.description - } : undefined, + description: this.description + ? { + type: 'buttons', + text: this.description, + } + : undefined, reset: { type: 'buttons', buttons: ['dialog.settings.reset_to_default'], click() { - dialog.setFormValues({input: setting.default_value}); - } - } + dialog.setFormValues({ input: setting.default_value }) + }, + }, }, - onConfirm({input}) { - setting.set(input); - Settings.save(); - this.hide().delete(); + onConfirm({ input }) { + setting.set(input) + Settings.save() + this.hide().delete() if (setting.requires_restart) { - Settings.showRestartMessage(); + Settings.showRestartMessage() } }, onCancel() { - this.hide().delete(); - } - }).show(); - + this.hide().delete() + }, + }).show() } } } @@ -309,154 +319,164 @@ export class SettingsProfile { name: string color: number condition: { - type: SettingsProfileConditionType, + type: SettingsProfileConditionType value: string } settings: Record selected: boolean constructor(data: SettingsProfileData = {}) { - this.uuid = guid(); - this.name = data.name || 'New Profile'; - this.color = data.color == undefined ? Math.randomInteger(0, markerColors.length-1) : data.color; + this.uuid = guid() + this.name = data.name || 'New Profile' + this.color = + data.color == undefined ? Math.randomInteger(0, markerColors.length - 1) : data.color this.condition = { type: SettingsProfileConditionType.selectable, - value: '' - }; - this.settings = {}; - this.extend(data); - this.selected = false; - SettingsProfile.all.push(this); + value: '', + } + this.settings = {} + this.extend(data) + this.selected = false + SettingsProfile.all.push(this) } select(update = true) { - if (this.condition.type !== SettingsProfileConditionType.selectable) return; + if (this.condition.type !== SettingsProfileConditionType.selectable) return + + SettingsProfile.all.forEach(p => (p.selected = false)) + this.selected = true + SettingsProfile.selected = this - SettingsProfile.all.forEach(p => p.selected = false); - this.selected = true; - SettingsProfile.selected = this; - if (update) { - Settings.updateSettingsInProfiles(); - Settings.saveLocalStorages(); - Settings.updateProfileButton(); + Settings.updateSettingsInProfiles() + Settings.saveLocalStorages() + Settings.updateProfileButton() } } extend(data) { - Merge.string(this, data, 'name'); + Merge.string(this, data, 'name') if (data.condition) { - this.condition.type = data.condition.type; - this.condition.value = data.condition.value; + this.condition.type = data.condition.type + this.condition.value = data.condition.value } if (data.settings) { for (let key in data.settings) { - let value = data.settings[key]; - if (value === undefined || value === null) continue; - Vue.set(this.settings, key, value); + let value = data.settings[key] + if (value === undefined || value === null) continue + Vue.set(this.settings, key, value) } } } isActive() { switch (this.condition.type) { case SettingsProfileConditionType.selectable: - return SettingsProfile.selected == this; + return SettingsProfile.selected == this case SettingsProfileConditionType.format: - if (Format && Format.id == this.condition.value) return true; - break; + if (Format && Format.id == this.condition.value) return true + break case SettingsProfileConditionType.file_path: - let regex = new RegExp(this.condition.value, 'i'); - if (Project && ( - regex.test(Project.save_path.replace(osfs, '/')) || - regex.test(Project.export_path.replace(osfs, '/')) - )) { - return true; + let regex = new RegExp(this.condition.value, 'i') + if ( + Project && + (regex.test(Project.save_path.replace(osfs, '/')) || + regex.test(Project.export_path.replace(osfs, '/'))) + ) { + return true } - break; + break } - return false; + return false } clear(key) { - Vue.delete(this.settings, key); - Settings.saveLocalStorages(); + Vue.delete(this.settings, key) + Settings.saveLocalStorages() } openDialog() { - let color_options = {}; + let color_options = {} for (let i = 0; i < markerColors.length; i++) { - color_options[i] = tl(`cube.color.${markerColors[i].id}`); + color_options[i] = tl(`cube.color.${markerColors[i].id}`) } let condition_types = { selectable: tl('settings_profile.condition.type.selectable'), format: tl('data.format'), file_path: tl('data.file_path'), - }; - let formats = {}; + } + let formats = {} for (let key in Formats) { - formats[key] = Formats[key].name; + formats[key] = Formats[key].name } let dialog = new Dialog({ id: 'settings_profile', title: tl('data.settings_profile'), form: { - name: {label: 'generic.name', type: 'text', value: this.name}, - color: {label: 'menu.cube.color', type: 'select', options: color_options, value: this.color}, + name: { label: 'generic.name', type: 'text', value: this.name }, + color: { + label: 'menu.cube.color', + type: 'select', + options: color_options, + value: this.color, + }, _1: '_', condition_type: { type: 'select', label: 'settings_profile.condition', value: this.condition.type, - options: condition_types + options: condition_types, }, format: { type: 'select', label: 'data.format', value: this.condition.type == 'format' ? this.condition.value : '', options: formats, - condition: (form) => form.condition_type == 'format' + condition: form => form.condition_type == 'format', }, file_path: { type: 'text', label: 'data.file_path', description: 'settings_profile.condition.type.file_path.desc', value: this.condition.type == 'file_path' ? this.condition.value : '', - condition: (form) => form.condition_type == 'file_path' + condition: form => form.condition_type == 'file_path', }, _2: '_', - remove: {type: 'buttons', buttons: ['generic.delete'], click: (button) => { - if (confirm(tl('settings_profile.confirm_delete'))) - this.remove(); - Settings.dialog.content_vue.$data.profile = null; - SettingsProfile.unselect(); - dialog.close(0); - }} + remove: { + type: 'buttons', + buttons: ['generic.delete'], + click: button => { + if (confirm(tl('settings_profile.confirm_delete'))) this.remove() + Settings.dialog.content_vue.$data.profile = null + SettingsProfile.unselect() + dialog.close(0) + }, + }, }, - onConfirm: (result) => { - this.name = result.name; - this.color = result.color; - this.condition.type = result.condition_type; - if (this.condition.type == 'format') this.condition.value = result.format; - if (this.condition.type == 'file_path') this.condition.value = result.file_path; - Settings.saveLocalStorages(); - Settings.updateProfileButton(); + onConfirm: result => { + this.name = result.name + this.color = result.color + this.condition.type = result.condition_type + if (this.condition.type == 'format') this.condition.value = result.format + if (this.condition.type == 'file_path') this.condition.value = result.file_path + Settings.saveLocalStorages() + Settings.updateProfileButton() }, onCancel() { - Settings.updateProfileButton(); - } - }).show(); + Settings.updateProfileButton() + }, + }).show() } remove() { - SettingsProfile.all.remove(this); - Settings.saveLocalStorages(); + SettingsProfile.all.remove(this) + Settings.saveLocalStorages() } static all: SettingsProfile[] = [] static selected: SettingsProfile | null = null - static unselect = function(update = true) { - SettingsProfile.all.forEach(p => p.selected = false); - SettingsProfile.selected = null; + static unselect = function (update = true) { + SettingsProfile.all.forEach(p => (p.selected = false)) + SettingsProfile.selected = null if (update) { - Settings.updateSettingsInProfiles(); - Settings.saveLocalStorages(); - Settings.updateProfileButton(); + Settings.updateSettingsInProfiles() + Settings.saveLocalStorages() + Settings.updateProfileButton() } } } @@ -466,17 +486,20 @@ export class SettingsProfile { */ export const Settings = { profile_menu_button: null as HTMLElement | null, - structure: {} as Record}>, + structure: {} as Record< + string, + { name: string; open: boolean; items: Record } + >, stored: {} as Record, dialog: null as Dialog | null, - addCategory(id: string, data: {name?: string, open?: boolean}) { + addCategory(id: string, data: { name?: string; open?: boolean }) { Settings.structure[id] = { - name: data.name || tl('settings.category.'+id), + name: data.name || tl('settings.category.' + id), open: data.open != undefined ? !!data.open : id === 'general', - items: {} + items: {}, } - Settings.dialog.sidebar.pages[id] = Settings.structure[id].name; - Settings.dialog.sidebar.build(); + Settings.dialog.sidebar.pages[id] = Settings.structure[id].name + Settings.dialog.sidebar.build() }, /** * Save all settings to the local storage @@ -484,10 +507,10 @@ export const Settings = { saveLocalStorages() { var settings_copy = {} for (var key in settings) { - settings_copy[key] = {value: settings[key].master_value} + settings_copy[key] = { value: settings[key].master_value } } - localStorage.setItem('settings', JSON.stringify(settings_copy) ) - localStorage.setItem('settings_profiles', JSON.stringify(SettingsProfile.all)); + localStorage.setItem('settings', JSON.stringify(settings_copy)) + localStorage.setItem('settings_profiles', JSON.stringify(SettingsProfile.all)) // @ts-ignore if (window.ColorPanel) ColorPanel.saveLocalStorages() @@ -497,46 +520,54 @@ export const Settings = { */ save() { Settings.saveLocalStorages() - updateSelection(); + updateSelection() for (let key in BarItems) { let action = BarItems[key] if (action instanceof Toggle && action.linked_setting) { - if (settings[action.linked_setting] && action.value != settings[action.linked_setting].value) { - action.value = settings[action.linked_setting].value as boolean; - action.updateEnabledState(); + if ( + settings[action.linked_setting] && + action.value != settings[action.linked_setting].value + ) { + action.value = settings[action.linked_setting].value as boolean + action.updateEnabledState() } } } - Settings.updateProfileButton(); - Blockbench.dispatchEvent('update_settings', {}); + Settings.updateProfileButton() + Blockbench.dispatchEvent('update_settings', {}) }, updateSettingsInProfiles() { - let settings_to_change = new Set(); + let settings_to_change = new Set() for (let profile of SettingsProfile.all) { for (let key in profile.settings) { if (settings[key]) { - settings_to_change.add(key); + settings_to_change.add(key) } else { - delete profile.settings[key]; + delete profile.settings[key] } } } settings_to_change.forEach((key: string) => { - let setting = settings[key]; - if (setting.onChange) setting.onChange(setting.value); + let setting = settings[key] + if (setting.onChange) setting.onChange(setting.value) }) }, updateProfileButton() { - let profile = SettingsProfile.selected; - Settings.profile_menu_button.style.color = profile ? markerColors[profile.color % markerColors.length].standard : ''; - Settings.profile_menu_button.classList.toggle('hidden', SettingsProfile.all.findIndex(p => p.condition.type == 'selectable') == -1); + let profile = SettingsProfile.selected + Settings.profile_menu_button.style.color = profile + ? markerColors[profile.color % markerColors.length].standard + : '' + Settings.profile_menu_button.classList.toggle( + 'hidden', + SettingsProfile.all.findIndex(p => p.condition.type == 'selectable') == -1 + ) }, import(file) { - let data = JSON.parse(file.content); + let data = JSON.parse(file.content) for (let key in settings) { - let setting = settings[key]; + let setting = settings[key] if (setting instanceof Setting && data.settings[key] !== undefined) { - setting.set(data.settings[key]); + setting.set(data.settings[key]) } } }, @@ -545,56 +576,58 @@ export const Settings = { */ get(id: string) { if (id && settings[id]) { - return settings[id].value; + return settings[id].value } }, - openDialog(options: {search_term?: string, profile?: string} = {}) { + openDialog(options: { search_term?: string; profile?: string } = {}) { for (var sett in settings) { if (settings.hasOwnProperty(sett)) { Settings.old[sett] = settings[sett].value } } - Settings.dialog.show(); - if (options.search_term) Settings.dialog.content_vue.$data.search_term = options.search_term; - if (options.profile) Settings.dialog.content_vue.$data.profile = options.profile; - Settings.dialog.content_vue.$forceUpdate(); + Settings.dialog.show() + if (options.search_term) Settings.dialog.content_vue.$data.search_term = options.search_term + if (options.profile) Settings.dialog.content_vue.$data.profile = options.profile + Settings.dialog.content_vue.$forceUpdate() }, showRestartMessage(settings?: Setting[]) { - let message; + let message if (settings instanceof Array) { - message = tl('message.settings_require_restart.message') + '\n\n'; + message = tl('message.settings_require_restart.message') + '\n\n' for (let setting of settings) { message += '* ' + setting.name + '\n' } } - Blockbench.showMessageBox({ - icon: 'fa-power-off', - translateKey: 'settings_require_restart', - message, - commands: { - restart_now: {text: 'message.settings_require_restart.restart_now'} + Blockbench.showMessageBox( + { + icon: 'fa-power-off', + translateKey: 'settings_require_restart', + message, + commands: { + restart_now: { text: 'message.settings_require_restart.restart_now' }, + }, + buttons: ['message.settings_require_restart.restart_later'], }, - buttons: ['message.settings_require_restart.restart_later'] - }, result => { - if (result == 'restart_now') { - if (isApp) { - Blockbench.once('before_closing', () => { - ipcRenderer.send('new-window'); - }) - window.close(); - } else { - location.reload(); + result => { + if (result == 'restart_now') { + if (isApp) { + Blockbench.once('before_closing', () => { + ipcRenderer.send('new-window') + }) + window.close() + } else { + location.reload() + } } } - }) + ) }, - old: {} + old: {}, } - Object.assign(window, { settings, Setting, SettingsProfile, Settings, -}); +}) diff --git a/js/interface/settings_window.ts b/js/interface/settings_window.ts index ad34f4e02..f7652f7c2 100644 --- a/js/interface/settings_window.ts +++ b/js/interface/settings_window.ts @@ -1,9 +1,9 @@ -import { Blockbench } from "../api"; -import { ipcRenderer } from "../native_apis"; -import { Plugins } from "../plugin_loader"; -import { compileJSON } from "../util/json"; -import { Dialog } from "./dialog"; -import { Setting, SettingsProfile } from "./settings"; +import { Blockbench } from '../api' +import { ipcRenderer } from '../native_apis' +import { Plugins } from '../plugin_loader' +import { compileJSON } from '../util/json' +import { Dialog } from './dialog' +import { Setting, SettingsProfile } from './settings' BARS.defineActions(() => { new Action('settings_window', { @@ -15,94 +15,106 @@ BARS.defineActions(() => { Settings.old[sett] = settings[sett].value } } - Settings.dialog.show(); - (document.querySelector('dialog#settings .search_bar > input') as HTMLElement).focus(); - } + Settings.dialog.show() + ;(document.querySelector('dialog#settings .search_bar > input') as HTMLElement).focus() + }, }) - + new Action('import_settings', { icon: 'folder', category: 'blockbench', click: function () { // @ts-ignore for now - Blockbench.import({ - resource_id: 'config', - extensions: ['bbsettings'], - type: 'Blockbench Settings' - }, function(files) { - Settings.import(files[0]); - }) - } + Blockbench.import( + { + resource_id: 'config', + extensions: ['bbsettings'], + type: 'Blockbench Settings', + }, + function (files) { + Settings.import(files[0]) + } + ) + }, }) new Action('export_settings', { icon: 'fas.fa-user-cog', category: 'blockbench', click: async function () { - let private_data = []; + let private_data = [] var settings_copy = {} for (var key in settings) { - settings_copy[key] = settings[key].value; + settings_copy[key] = settings[key].value if (settings[key].value && settings[key].type == 'password') { - private_data.push(key); + private_data.push(key) } } if (private_data.length) { let go_on = await new Promise((resolve, reject) => { - Blockbench.showMessageBox({ - title: 'dialog.export_private_settings.title', - message: tl('dialog.export_private_settings.message', [private_data.map(key => settings[key].name).join(', ')]), - buttons: ['dialog.export_private_settings.keep', 'dialog.export_private_settings.omit', 'dialog.cancel'] - }, result => { - if (result == 1) { - private_data.forEach(key => { - delete settings_copy[key]; - }) + Blockbench.showMessageBox( + { + title: 'dialog.export_private_settings.title', + message: tl('dialog.export_private_settings.message', [ + private_data.map(key => settings[key].name).join(', '), + ]), + buttons: [ + 'dialog.export_private_settings.keep', + 'dialog.export_private_settings.omit', + 'dialog.cancel', + ], + }, + result => { + if (result == 1) { + private_data.forEach(key => { + delete settings_copy[key] + }) + } + resolve(result !== 2) } - resolve(result !== 2); - }) - }); - if (!go_on) return; + ) + }) + if (!go_on) return } // @ts-ignore for now Blockbench.export({ resource_id: 'config', type: 'Blockbench Settings', extensions: ['bbsettings'], - content: compileJSON({settings: settings_copy}) + content: compileJSON({ settings: settings_copy }), }) - } + }, }) - let title_bar = document.getElementById('settings_title_bar'); - BarItems.import_settings.toElement(title_bar); - BarItems.export_settings.toElement(title_bar); + let title_bar = document.getElementById('settings_title_bar') + BarItems.import_settings.toElement(title_bar) + BarItems.export_settings.toElement(title_bar) }) -onVueSetup(function() { +onVueSetup(function () { for (var key in settings) { - if (settings[key].condition == false) continue; + if (settings[key].condition == false) continue var category = settings[key].category if (!category) category = 'general' if (!Settings.structure[category]) { Settings.structure[category] = { - name: tl('settings.category.'+category), + name: tl('settings.category.' + category), open: category === 'general', - items: {} + items: {}, } } Settings.structure[category].items[key] = settings[key] } - let sidebar_pages = {}; + let sidebar_pages = {} for (let key in Settings.structure) { - sidebar_pages[key] = Settings.structure[key].name; + sidebar_pages[key] = Settings.structure[key].name } interface SettingsDialogVueData { - structure: any, - profile: null | SettingsProfile, - all_profiles: SettingsProfile[], - open_category: string, + structure: any + profile: null | SettingsProfile + all_profiles: SettingsProfile[] + open_category: string search_term: string } Settings.dialog = new Dialog({ @@ -119,26 +131,25 @@ onVueSetup(function() { sidebar: { pages: sidebar_pages, page: 'general', - actions: [ - 'import_settings', - 'export_settings', - ], + actions: ['import_settings', 'export_settings'], onPageSwitch(page) { - Settings.dialog.content_vue.open_category = page; - Settings.dialog.content_vue.search_term = ''; - } + Settings.dialog.content_vue.open_category = page + Settings.dialog.content_vue.search_term = '' + }, }, component: { - data() {return { - structure: Settings.structure, - profile: null, - all_profiles: SettingsProfile.all, - open_category: 'general', - search_term: '' - } as SettingsDialogVueData}, + data() { + return { + structure: Settings.structure, + profile: null, + all_profiles: SettingsProfile.all, + open_category: 'general', + search_term: '', + } as SettingsDialogVueData + }, methods: { saveSettings(this: SettingsDialogVueData) { - Settings.saveLocalStorages(); + Settings.saveLocalStorages() }, settingContextMenu(setting: Setting, event: MouseEvent) { new Menu([ @@ -146,11 +157,11 @@ onVueSetup(function() { name: 'dialog.settings.reset_to_default', icon: 'replay', click: () => { - setting.ui_value = setting.default_value; - this.saveSettings(); - } - } - ]).open(event); + setting.ui_value = setting.default_value + this.saveSettings() + }, + }, + ]).open(event) }, showProfileMenu(this: SettingsDialogVueData) { let items: MenuItem[] = [ @@ -159,111 +170,112 @@ onVueSetup(function() { icon: 'remove', color: '', click: () => { - this.profile = null; - } - } - ]; + this.profile = null + }, + }, + ] SettingsProfile.all.forEach(profile => { items.push({ name: profile.name, icon: 'manage_accounts', color: markerColors[profile.color % markerColors.length].standard, click: () => { - this.profile = profile; + this.profile = profile if (profile.condition.type == 'selectable') { - profile.select(); + profile.select() } else { - SettingsProfile.unselect(); + SettingsProfile.unselect() } - } + }, }) }) - items.push( - '_', - {name: 'dialog.settings.create_profile', icon: 'add', click: () => { - this.profile = new SettingsProfile({}); - this.profile.openDialog(); - }} - ) + items.push('_', { + name: 'dialog.settings.create_profile', + icon: 'add', + click: () => { + this.profile = new SettingsProfile({}) + this.profile.openDialog() + }, + }) // @ts-ignore new Menu('settings_profiles', items).open(this.$refs.profile_menu) }, profileButtonPress(this: SettingsDialogVueData) { if (!this.profile) { - this.profile = new SettingsProfile({}); + this.profile = new SettingsProfile({}) } - this.profile.openDialog(); + this.profile.openDialog() }, getProfileValuesForSetting(this: SettingsDialogVueData, key) { return this.all_profiles.filter(profile => { - return profile.settings[key] !== undefined; - }); + return profile.settings[key] !== undefined + }) }, getProfileColor(profile?: SettingsProfile): string { if (profile && markerColors[profile.color % markerColors.length]) { return markerColors[profile.color % markerColors.length].standard } - return ''; + return '' }, isFullWidth(setting: Setting): boolean { - return ['text', 'password', 'select'].includes(setting.type); + return ['text', 'password', 'select'].includes(setting.type) }, getPluginName(plugin_id: string): string { - let plugin = Plugins.all.find(p => p.id == plugin_id); - return plugin?.title ?? plugin_id; + let plugin = Plugins.all.find(p => p.id == plugin_id) + return plugin?.title ?? plugin_id }, revealPlugin(plugin_id: string) { - let plugin = Plugins.all.find(p => p.id == plugin_id); - if (!plugin) return; - - Plugins.dialog.show(); - Plugins.dialog.content_vue.selectPlugin!(plugin); + let plugin = Plugins.all.find(p => p.id == plugin_id) + if (!plugin) return + + Plugins.dialog.show() + Plugins.dialog.content_vue.selectPlugin!(plugin) }, getIconNode: Blockbench.getIconNode, tl, - Condition + Condition, }, computed: { list() { if (this.search_term) { - var keywords = this.search_term.toLowerCase().replace(/_/g, ' ').split(' '); - var items = {}; + var keywords = this.search_term.toLowerCase().replace(/_/g, ' ').split(' ') + var items = {} for (var key in settings) { - var setting = settings[key]; + var setting = settings[key] if (Condition(setting.condition)) { - var name = setting.name.toLowerCase(); - var desc = setting.description.toLowerCase(); - var missmatch = false; + var name = setting.name.toLowerCase() + var desc = setting.description.toLowerCase() + var missmatch = false for (var word of keywords) { if ( !key.includes(word) && !name.includes(word) && !desc.includes(word) ) { - missmatch = true; + missmatch = true } } if (!missmatch) { - items[key] = setting; + items[key] = setting } } } - return items; + return items } else { - return this.structure[this.open_category].items; + return this.structure[this.open_category].items } }, title() { if (this.search_term) { - return tl('dialog.settings.search_results'); + return tl('dialog.settings.search_results') } else { - return this.structure[this.open_category].name; + return this.structure[this.open_category].name } }, profile_name() { - return this.profile ? this.profile.name : tl('generic.none'); - } + return this.profile ? this.profile.name : tl('generic.none') + }, }, template: `
@@ -334,32 +346,32 @@ onVueSetup(function() { -
` +
`, }, onButton() { - Settings.save(); + Settings.save() function hasSettingChanged(id) { - return (Settings.old && settings[id].value !== Settings.old[id]) + return Settings.old && settings[id].value !== Settings.old[id] } - let changed_settings = []; + let changed_settings = [] for (let id in settings) { - let setting = settings[id]; - if (!Condition(setting.condition)) continue; - let has_changed = hasSettingChanged(id); + let setting = settings[id] + if (!Condition(setting.condition)) continue + let has_changed = hasSettingChanged(id) if (has_changed) { - changed_settings.push(setting); + changed_settings.push(setting) if (setting.onChange) { - setting.onChange(setting.value); + setting.onChange(setting.value) } if (isApp && setting.launch_setting) { - ipcRenderer.send('edit-launch-setting', {key: id, value: setting.value}) + ipcRenderer.send('edit-launch-setting', { key: id, value: setting.value }) } } } - let restart_settings = changed_settings.filter(setting => setting.requires_restart); + let restart_settings = changed_settings.filter(setting => setting.requires_restart) if (restart_settings.length) { - Settings.showRestartMessage(restart_settings); + Settings.showRestartMessage(restart_settings) } - } + }, }) -}) \ No newline at end of file +}) diff --git a/js/interface/themes.ts b/js/interface/themes.ts index 2ad4104ba..9d0b09022 100644 --- a/js/interface/themes.ts +++ b/js/interface/themes.ts @@ -11,7 +11,7 @@ import { InputFormConfig } from './form' import { Filesystem } from '../file_system' import { fs } from '../native_apis' -type ThemeSource = 'built_in' | 'file' | 'repository' | 'custom'; +type ThemeSource = 'built_in' | 'file' | 'repository' | 'custom' type ThemeData = { name: string author: string @@ -69,10 +69,10 @@ export class CustomTheme { headline_font: string code_font: string colors: Record - source?: 'built_in' | 'file' | 'repository' | 'custom'; + source?: 'built_in' | 'file' | 'repository' | 'custom' path?: string desktop_only?: boolean - options: null|{ + options: null | { [key: string]: { name: string options: Record @@ -81,21 +81,21 @@ export class CustomTheme { option_values: Record constructor(data?: Partial) { - this.id = ''; - this.name = ''; - this.author = ''; - this.main_font = ''; - this.headline_font = ''; - this.code_font = ''; - this.borders = false; - this.thumbnail = ''; - this.css = ''; - this.colors = structuredClone(DEFAULT_COLORS); - this.options = null; - this.option_values = {}; + this.id = '' + this.name = '' + this.author = '' + this.main_font = '' + this.headline_font = '' + this.code_font = '' + this.borders = false + this.thumbnail = '' + this.css = '' + this.colors = structuredClone(DEFAULT_COLORS) + this.options = null + this.option_values = {} if (data) { - this.extend(data); + this.extend(data) } } extend(data: Partial) { @@ -110,29 +110,29 @@ export class CustomTheme { Merge.string(this, data, 'main_font') Merge.string(this, data, 'headline_font') Merge.string(this, data, 'code_font') - Merge.string(this, data, 'css'); - Merge.string(this, data, 'thumbnail'); + Merge.string(this, data, 'css') + Merge.string(this, data, 'thumbnail') if (data.colors) { for (let key in this.colors) { if (data.colors[key]) { - Merge.string(this.colors, data.colors, key); + Merge.string(this.colors, data.colors, key) } else { - CustomTheme.selected.colors[key] = DEFAULT_COLORS[key]; + CustomTheme.selected.colors[key] = DEFAULT_COLORS[key] } } } - if (data.options) this.options = structuredClone(data.options); - if (data.option_values) this.option_values = Object.assign({}, data.option_values); + if (data.options) this.options = structuredClone(data.options) + if (data.option_values) this.option_values = Object.assign({}, data.option_values) } openOptions() { - let form: InputFormConfig = {}; - if (!this.options) return; - if (CustomTheme.selected != this) this.load(); - let theme = this; + let form: InputFormConfig = {} + if (!this.options) return + if (CustomTheme.selected != this) this.load() + let theme = this for (let key in this.options) { - let opt = this.options[key]; - let chars = Object.keys(opt.options).reduce((val, key) => val + opt.options[key].length); + let opt = this.options[key] + let chars = Object.keys(opt.options).reduce((val, key) => val + opt.options[key].length) form[key] = { label: opt.name, type: chars.length > 28 ? 'select' : 'inline_select', @@ -146,35 +146,30 @@ export class CustomTheme { form, singleButton: true, onFormChange(result: Record) { - theme.option_values = result; - CustomTheme.updateSettings(); - theme.save(); + theme.option_values = result + CustomTheme.updateSettings() + theme.save() }, - }).show(); + }).show() } - static selected: CustomTheme = new CustomTheme({id: 'dark'}) + static selected: CustomTheme = new CustomTheme({ id: 'dark' }) static get data(): CustomTheme { return CustomTheme.selected } static backup_data: string | null = null - static themes: CustomTheme[] = [ - DarkTheme, - LightTheme, - ContrastTheme - ].map(theme_data => { - let theme = new CustomTheme().parseBBTheme(theme_data, true); - theme.source = 'built_in'; - return theme; + static themes: CustomTheme[] = [DarkTheme, LightTheme, ContrastTheme].map(theme_data => { + let theme = new CustomTheme().parseBBTheme(theme_data, true) + theme.source = 'built_in' + return theme }) static defaultColors = DEFAULT_COLORS static sideloaded_themes: string[] = [] - static dialog: Dialog|null = null + static dialog: Dialog | null = null static setup() { - fs - const theme_watchers: Record = {}; - let remote_themes_loaded = false; + const theme_watchers: Record = {} + let remote_themes_loaded = false CustomTheme.dialog = new Dialog({ id: 'theme', title: 'dialog.settings.theme', @@ -201,39 +196,43 @@ export class CustomTheme { name: 'layout.documentation', icon: 'fa-book', click() { - Blockbench.openLink('https://www.blockbench.net/wiki/blockbench/themes'); - } + Blockbench.openLink('https://www.blockbench.net/wiki/blockbench/themes') + }, }, 'import_theme', 'export_theme', ], onPageSwitch(page) { - CustomTheme.dialog.content_vue.open_category = page; + CustomTheme.dialog.content_vue.open_category = page if (page == 'color' && !CustomTheme.dialog_is_setup) { CustomTheme.setupDialog() } - } + }, }, onOpen() { - this.content_vue.data = CustomTheme.data; + this.content_vue.data = CustomTheme.data if (!remote_themes_loaded) { - remote_themes_loaded = true; - $.getJSON('https://api.github.com/repos/JannisX11/blockbench-themes/contents/themes').then(files => { - files.forEach(async (file) => { - try { - let {content} = await $.getJSON(file.git_url); - let theme = new CustomTheme().parseBBTheme(patchedAtob(content)); - if (theme.desktop_only && Blockbench.isMobile) return false; - theme.id = file.name.replace(/\.\w+/, ''); - theme.source = 'repository'; - if (!CustomTheme.themes.find(t2 => t2.id == theme.id)) { - CustomTheme.themes.push(theme); + remote_themes_loaded = true + $.getJSON( + 'https://api.github.com/repos/JannisX11/blockbench-themes/contents/themes' + ) + .then(files => { + files.forEach(async file => { + try { + let { content } = await $.getJSON(file.git_url) + let theme = new CustomTheme().parseBBTheme(patchedAtob(content)) + if (theme.desktop_only && Blockbench.isMobile) return false + theme.id = file.name.replace(/\.\w+/, '') + theme.source = 'repository' + if (!CustomTheme.themes.find(t2 => t2.id == theme.id)) { + CustomTheme.themes.push(theme) + } + } catch (err) { + console.error(err) } - } catch (err) { - console.error(err); - } + }) }) - }).catch(console.error) + .catch(console.error) } }, component: { @@ -246,88 +245,88 @@ export class CustomTheme { built_in: '', repository: 'globe', file: 'draft', - } + }, }, components: { - VuePrismEditor + VuePrismEditor, }, watch: { 'data.main_font'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.headline_font'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.code_font'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.borders'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.css'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.thumbnail'() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, 'data.colors': { handler() { - CustomTheme.updateSettings(); - CustomTheme.selected.save(); + CustomTheme.updateSettings() + CustomTheme.selected.save() }, - deep: true - } + deep: true, + }, }, methods: { selectTheme(theme: CustomTheme) { - theme.load(); - this.data = theme; - CustomTheme.selected.save(); + theme.load() + this.data = theme + CustomTheme.selected.save() }, loadBackup() { - let theme = new CustomTheme(JSON.parse(CustomTheme.backup_data)); - theme.load(); - this.clearBackup(); + let theme = new CustomTheme(JSON.parse(CustomTheme.backup_data)) + theme.load() + this.clearBackup() }, clearBackup() { - this.backup = ''; - CustomTheme.backup_data = null; + this.backup = '' + CustomTheme.backup_data = null }, customizeTheme() { - CustomTheme.customizeTheme(); + CustomTheme.customizeTheme() }, getThemeThumbnailStyle(theme: CustomTheme) { - let style = {}; + let style = {} for (let key in CustomTheme.defaultColors) { - style[`--color-${key}`] = CustomTheme.defaultColors[key]; + style[`--color-${key}`] = CustomTheme.defaultColors[key] } for (let key in theme.colors) { - style[`--color-${key}`] = theme.colors[key]; + style[`--color-${key}`] = theme.colors[key] } - return style; + return style }, openContextMenu(theme: CustomTheme, event: MouseEvent) { - if (theme.source != 'file') return; - let selected = theme.id == this.data.id; + if (theme.source != 'file') return + let selected = theme.id == this.data.id let menu = new Menu([ { name: 'menu.texture.folder', icon: 'folder', condition: isApp, click: () => { - if (!isApp || !theme.path) return; + if (!isApp || !theme.path) return if (!fs.existsSync(theme.path)) { - Blockbench.showQuickMessage('texture.error.file'); - return; + Blockbench.showQuickMessage('texture.error.file') + return } - Filesystem.showFileInFolder(theme.path); - } + Filesystem.showFileInFolder(theme.path) + }, }, { name: 'layout.file.watch_changes', @@ -335,55 +334,60 @@ export class CustomTheme { condition: isApp && selected, click: () => { if (theme_watchers[theme.path]) { - theme_watchers[theme.path].close(); - delete theme_watchers[theme.path]; - + theme_watchers[theme.path].close() + delete theme_watchers[theme.path] } else if (fs.existsSync(theme.path)) { - let timeout: number = 0; - theme_watchers[theme.path] = fs.watch(theme.path, (eventType) => { - if (eventType == 'change') { - if (timeout) { - clearTimeout(timeout); - timeout = 0; + let timeout: number = 0 + theme_watchers[theme.path] = fs.watch( + theme.path, + eventType => { + if (eventType == 'change') { + if (timeout) { + clearTimeout(timeout) + timeout = 0 + } + timeout = window.setTimeout(() => { + theme.reloadThemeFile() + }, 60) } - timeout = window.setTimeout(() => { - theme.reloadThemeFile(); - }, 60) } - }) + ) } - } + }, }, { name: 'generic.reload', icon: 'refresh', condition: isApp && selected, click: () => { - theme.reloadThemeFile(); - } + theme.reloadThemeFile() + }, }, { name: 'generic.remove', icon: 'clear', click: () => { - this.themes.remove(theme); - CustomTheme.sideloaded_themes.remove(theme.path); - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); - } - } + this.themes.remove(theme) + CustomTheme.sideloaded_themes.remove(theme.path) + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ) + }, + }, ]) - menu.open(event); + menu.open(event) }, - tl + tl, }, computed: { listed_themes() { - let themes = this.themes.slice(); + let themes = this.themes.slice() if (this.data.source == 'custom') { - themes.splice(0, 0, this.data); + themes.splice(0, 0, this.data) } - return themes; - } + return themes + }, }, template: `
@@ -518,23 +522,23 @@ export class CustomTheme {
-
` +
`, }, onButton() { - Settings.save(); - } + Settings.save() + }, }) } static setupDialog() { - let wrapper = $('#color_wrapper'); + let wrapper = $('#color_wrapper') for (let key in CustomTheme.defaultColors) { - let scope_key = key; - let hex = CustomTheme.selected.colors[scope_key]; - let last_color = hex; - let field = wrapper.find(`#color_field_${scope_key} .layout_color_preview`); + let scope_key = key + let hex = CustomTheme.selected.colors[scope_key] + let last_color = hex + let field = wrapper.find(`#color_field_${scope_key} .layout_color_preview`) field.spectrum({ - preferredFormat: "hex", + preferredFormat: 'hex', color: hex, showAlpha: false, showInput: true, @@ -544,232 +548,259 @@ export class CustomTheme { cancelText: tl('dialog.cancel'), chooseText: tl('dialog.confirm'), move(c) { - CustomTheme.selected.colors[scope_key] = c.toHexString(); - CustomTheme.customizeTheme(); + CustomTheme.selected.colors[scope_key] = c.toHexString() + CustomTheme.customizeTheme() }, change(c) { - last_color = c.toHexString(); + last_color = c.toHexString() }, hide(c) { - CustomTheme.selected.colors[scope_key] = last_color; - field.spectrum('set', last_color); + CustomTheme.selected.colors[scope_key] = last_color + field.spectrum('set', last_color) }, beforeShow() { - last_color = CustomTheme.selected.colors[scope_key]; - field.spectrum('set', last_color); - } - }); + last_color = CustomTheme.selected.colors[scope_key] + field.spectrum('set', last_color) + }, + }) } - CustomTheme.dialog_is_setup = true; + CustomTheme.dialog_is_setup = true } - static dialog_is_setup = false; + static dialog_is_setup = false static customizeTheme() { if (CustomTheme.selected.source != 'custom') { - let theme = new CustomTheme(CustomTheme.selected); + let theme = new CustomTheme(CustomTheme.selected) theme.extend({ - name: theme.name ? ('Copy of ' + theme.name) : 'Custom Theme', + name: theme.name ? 'Copy of ' + theme.name : 'Custom Theme', author: settings.username.value as string, id: 'custom_theme', source: 'custom', }) - let i = 0; + let i = 0 while (CustomTheme.themes.find(t2 => theme.id == t2.id)) { - i++; - theme.id = 'custom_theme_'+i; + i++ + theme.id = 'custom_theme_' + i } - if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = theme; - theme.load(); + if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = theme + theme.load() } } - static updateColors() { - $('meta[name=theme-color]').attr('content', CustomTheme.selected.colors.frame); - document.body.classList.toggle('light_mode', new tinycolor(CustomTheme.selected.colors.ui).isLight()); - - let gizmo_colors = Canvas.gizmo_colors; + static updateColors() { + $('meta[name=theme-color]').attr('content', CustomTheme.selected.colors.frame) + document.body.classList.toggle( + 'light_mode', + new tinycolor(CustomTheme.selected.colors.ui).isLight() + ) + + let gizmo_colors = Canvas.gizmo_colors if (typeof gizmo_colors != 'undefined') { - let preview_style = window.getComputedStyle(document.getElementById('preview')); + let preview_style = window.getComputedStyle(document.getElementById('preview')) function update(three_color, variable) { - let string = preview_style.getPropertyValue(variable).trim(); - three_color.set(string); + let string = preview_style.getPropertyValue(variable).trim() + three_color.set(string) } - update(gizmo_colors.r, '--color-axis-x'); - update(gizmo_colors.g, '--color-axis-y'); - update(gizmo_colors.b, '--color-axis-z'); - update(gizmo_colors.grid, '--color-grid'); - update(gizmo_colors.u, '--color-axis-u'); // spline space colors - update(gizmo_colors.v, '--color-axis-v'); // spline space colors - update(gizmo_colors.w, '--color-axis-w'); // spline space colors - update(Canvas.gridMaterial.color, '--color-grid'); - update(Canvas.wireframeMaterial.color, '--color-wireframe'); - update(gizmo_colors.solid, '--color-solid'); - update(gizmo_colors.outline, '--color-outline'); - update(gizmo_colors.gizmo_hover, '--color-gizmohover'); - update(Canvas.outlineMaterial.color, '--color-outline'); - update((Canvas.ground_plane.material as THREE.MeshBasicMaterial).color, '--color-ground'); - update((Canvas.brush_outline.material as THREE.ShaderMaterial).uniforms.color.value, '--color-brush-outline'); - update(gizmo_colors.spline_handle_aligned, '--color-spline-handle-aligned'); - update(gizmo_colors.spline_handle_mirrored, '--color-spline-handle-mirrored'); - update(gizmo_colors.spline_handle_free, '--color-spline-handle-free'); - + update(gizmo_colors.r, '--color-axis-x') + update(gizmo_colors.g, '--color-axis-y') + update(gizmo_colors.b, '--color-axis-z') + update(gizmo_colors.grid, '--color-grid') + update(gizmo_colors.u, '--color-axis-u') // spline space colors + update(gizmo_colors.v, '--color-axis-v') // spline space colors + update(gizmo_colors.w, '--color-axis-w') // spline space colors + update(Canvas.gridMaterial.color, '--color-grid') + update(Canvas.wireframeMaterial.color, '--color-wireframe') + update(gizmo_colors.solid, '--color-solid') + update(gizmo_colors.outline, '--color-outline') + update(gizmo_colors.gizmo_hover, '--color-gizmohover') + update(Canvas.outlineMaterial.color, '--color-outline') + update( + (Canvas.ground_plane.material as THREE.MeshBasicMaterial).color, + '--color-ground' + ) + update( + (Canvas.brush_outline.material as THREE.ShaderMaterial).uniforms.color.value, + '--color-brush-outline' + ) + update(gizmo_colors.spline_handle_aligned, '--color-spline-handle-aligned') + update(gizmo_colors.spline_handle_mirrored, '--color-spline-handle-mirrored') + update(gizmo_colors.spline_handle_free, '--color-spline-handle-free') + Canvas.pivot_marker.children.forEach(c => { // @ts-ignore - c.updateColors(); + c.updateColors() }) } } static updateSettings() { - let theme = CustomTheme.selected; - let variables = {}; + let theme = CustomTheme.selected + let variables = {} for (let key in CustomTheme.selected.colors) { - variables['--color-'+key] = CustomTheme.selected.colors[key]; + variables['--color-' + key] = CustomTheme.selected.colors[key] } - variables['--font-custom-main'] = `'${theme.main_font}'`; - variables['--font-custom-headline'] = `'${theme.headline_font}'`; - variables['--font-custom-code'] = `'${theme.code_font}'`; - let variable_section = `body {\n`; + variables['--font-custom-main'] = `'${theme.main_font}'` + variables['--font-custom-headline'] = `'${theme.headline_font}'` + variables['--font-custom-code'] = `'${theme.code_font}'` + let variable_section = `body {\n` for (let key in variables) { variable_section += `\n\t${key}: ${variables[key]};` } - variable_section += '\n}\n'; - document.getElementById('theme_css').textContent = `@layer theme {${variable_section}${theme.css}};` - document.body.classList.toggle('theme_borders', !!theme.borders); + variable_section += '\n}\n' + document.getElementById('theme_css').textContent = + `@layer theme {${variable_section}${theme.css}};` + document.body.classList.toggle('theme_borders', !!theme.borders) // Options for (let attribute of document.body.attributes) { - if (attribute.name.startsWith('theme-') && (!theme.options || theme.options[attribute.name.substring(6)] == undefined)) { - document.body.removeAttribute(attribute.name); + if ( + attribute.name.startsWith('theme-') && + (!theme.options || theme.options[attribute.name.substring(6)] == undefined) + ) { + document.body.removeAttribute(attribute.name) } } - for (let key in (theme.options??{})) { - if (theme.option_values[key] == undefined) continue; - document.body.setAttribute('theme-'+key, theme.option_values[key]); + for (let key in theme.options ?? {}) { + if (theme.option_values[key] == undefined) continue + document.body.setAttribute('theme-' + key, theme.option_values[key]) } - CustomTheme.loadThumbnailStyles(); - CustomTheme.updateColors(); + CustomTheme.loadThumbnailStyles() + CustomTheme.updateColors() } static loadThumbnailStyles() { // @ts-ignore - let split_regex = (isApp || window.chrome) ? new RegExp('(? `[theme_id="${theme.id}"] ${e.trim()}`).join(", "); - thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; + const selector = (rule as CSSStyleRule).selectorText + .split(split_regex) + .map(e => `[theme_id="${theme.id}"] ${e.trim()}`) + .join(', ') + thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n` } } if (CustomTheme.selected.source == 'custom') { - style.textContent = CustomTheme.selected.thumbnail; - const sheet = style.sheet; + style.textContent = CustomTheme.selected.thumbnail + const sheet = style.sheet for (const rule of sheet.cssRules) { - if (!(rule as CSSStyleRule).selectorText) continue; - - const selector = (rule as CSSStyleRule).selectorText.split(split_regex).map(e => `[theme_id="${CustomTheme.selected.id}"] ${e.trim()}`).join(", "); - thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; + if (!(rule as CSSStyleRule).selectorText) continue + + const selector = (rule as CSSStyleRule).selectorText + .split(split_regex) + .map(e => `[theme_id="${CustomTheme.selected.id}"] ${e.trim()}`) + .join(', ') + thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n` } } - document.head.removeChild(style); - document.getElementById('theme_thumbnail_css').textContent = thumbnailStyles; + document.head.removeChild(style) + document.getElementById('theme_thumbnail_css').textContent = thumbnailStyles } load() { if (CustomTheme.selected.source == 'custom' && CustomTheme.selected.name) { // Backup - if (!CustomTheme.dialog.content_vue) CustomTheme.dialog.build(); - CustomTheme.dialog.content_vue.backup = CustomTheme.selected.name; - CustomTheme.backup_data = JSON.stringify(CustomTheme.selected); + if (!CustomTheme.dialog.content_vue) CustomTheme.dialog.build() + CustomTheme.dialog.content_vue.backup = CustomTheme.selected.name + CustomTheme.backup_data = JSON.stringify(CustomTheme.selected) } - CustomTheme.selected = this; - if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = this; + CustomTheme.selected = this + if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = this - CustomTheme.updateSettings(); - this.save(); + CustomTheme.updateSettings() + this.save() } static loadTheme(theme: CustomTheme) { - theme.load(); + theme.load() } reloadThemeFile() { - let content = fs.readFileSync(this.path, {encoding: 'utf8'}); - if (!content) return; - let new_theme = new CustomTheme().parseBBTheme(content); - delete new_theme.id; - delete new_theme.option_values; - this.extend(new_theme); - this.load(); + let content = fs.readFileSync(this.path, { encoding: 'utf8' }) + if (!content) return + let new_theme = new CustomTheme().parseBBTheme(content) + delete new_theme.id + delete new_theme.option_values + this.extend(new_theme) + this.load() } static import(file: Filesystem.FileResult) { - let content = file.content as string; + let content = file.content as string - let theme = new CustomTheme().parseBBTheme(content); + let theme = new CustomTheme().parseBBTheme(content) - theme.id = file.name.replace(/\.\w+$/, ''); - if (!theme.name) theme.name = theme.id; + theme.id = file.name.replace(/\.\w+$/, '') + if (!theme.name) theme.name = theme.id - theme.source = 'file'; - theme.path = file.path; + theme.source = 'file' + theme.path = file.path - CustomTheme.loadTheme(theme); - CustomTheme.themes.remove(CustomTheme.themes.find((t2: CustomTheme) => t2.id == theme.id)); - CustomTheme.themes.push(theme); + CustomTheme.loadTheme(theme) + CustomTheme.themes.remove(CustomTheme.themes.find((t2: CustomTheme) => t2.id == theme.id)) + CustomTheme.themes.push(theme) if (isApp) { - CustomTheme.sideloaded_themes.safePush(file.path); - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); + CustomTheme.sideloaded_themes.safePush(file.path) + localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)) } } parseBBTheme(content: string, include_id?: boolean): this { if (content.startsWith('{')) { // Lecagy format - let json_data = JSON.parse(content); - this.extend(json_data); - + let json_data = JSON.parse(content) + this.extend(json_data) } else { function extractSection(start_match: string, end_match: string): string | undefined { - content = content.trim(); - if (content.startsWith(start_match) == false) return; - let end = content.indexOf(end_match); - let section = content.substring(start_match.length, end); - content = content.substring(end+end_match.length); - return section; + content = content.trim() + if (content.startsWith(start_match) == false) return + let end = content.indexOf(end_match) + let section = content.substring(start_match.length, end) + content = content.substring(end + end_match.length) + return section } - let metadata_section = extractSection('/*', '*/'); + let metadata_section = extractSection('/*', '*/') if (metadata_section) { - let metadata = BBYaml.parse(metadata_section); - this.extend(metadata); + let metadata = BBYaml.parse(metadata_section) + this.extend(metadata) } - let variable_section = extractSection('body {', '}'); + let variable_section = extractSection('body {', '}') if (variable_section) { for (let line of variable_section.split(/\r?\n/)) { - line = line.trim(); + line = line.trim() if (line.startsWith('--color')) { - let [key, value] = line.replace('--color-', '').split(/:\s*/); - this.colors[key] = value.replace(/;/, ''); + let [key, value] = line.replace('--color-', '').split(/:\s*/) + this.colors[key] = value.replace(/;/, '') } else if (line.startsWith('--font-custom')) { - let [key, value] = line.replace('--font-custom-', '').split(/:\s*/); - value = value.replace(';', ''); + let [key, value] = line.replace('--font-custom-', '').split(/:\s*/) + value = value.replace(';', '') switch (key) { - case 'main': this.main_font = value; break; - case 'headline': this.headline_font = value; break; - case 'code': this.code_font = value; break; + case 'main': + this.main_font = value + break + case 'headline': + this.headline_font = value + break + case 'code': + this.code_font = value + break } } } } - this.thumbnail = extractSection('@scope (thumbnail) {', '\n}') ?? ''; + this.thumbnail = extractSection('@scope (thumbnail) {', '\n}') ?? '' - this.css = content; + this.css = content } - return this; + return this } compileBBTheme(): string { - let theme = '/*'; + let theme = '/*' let metadata = { name: this.name, author: this.author, @@ -777,66 +808,66 @@ export class CustomTheme { borders: this.borders, } for (let key in metadata) { - if (metadata[key] == undefined) continue; - theme += `\n${key}: ${metadata[key].toString()}`; + if (metadata[key] == undefined) continue + theme += `\n${key}: ${metadata[key].toString()}` } - theme += '\n*/\n'; + theme += '\n*/\n' // Variables - theme += 'body {'; + theme += 'body {' for (let color in this.colors) { - let color_value = this.colors[color]; - theme += `\n\t--color-${color}: ${color_value};`; + let color_value = this.colors[color] + theme += `\n\t--color-${color}: ${color_value};` } - if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};`; - if (this.headline_font) theme += `\n\t--font-custom-headline: ${this.headline_font};`; - if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};`; + if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};` + if (this.headline_font) theme += `\n\t--font-custom-headline: ${this.headline_font};` + if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};` - theme += '\n}\n'; + theme += '\n}\n' if (this.thumbnail) { theme += '@scope (thumbnail) {\n' - theme += this.thumbnail.replace(/\n/g, '\n\t'); - theme += '\n}\n'; + theme += this.thumbnail.replace(/\n/g, '\n\t') + theme += '\n}\n' } if (this.css) { - theme += this.css; + theme += this.css } - return theme; + return theme } save() { - localStorage.setItem('theme', JSON.stringify(this)); + localStorage.setItem('theme', JSON.stringify(this)) } -}; +} if (isApp && localStorage.getItem('themes_sideloaded')) { try { - let sideloaded = JSON.parse(localStorage.getItem('themes_sideloaded')); + let sideloaded = JSON.parse(localStorage.getItem('themes_sideloaded')) if (sideloaded instanceof Array && sideloaded.length) { - CustomTheme.sideloaded_themes = sideloaded; + CustomTheme.sideloaded_themes = sideloaded CustomTheme.sideloaded_themes.forEachReverse(path => { if (!fs.existsSync(path)) { - CustomTheme.sideloaded_themes.remove(path); + CustomTheme.sideloaded_themes.remove(path) } }) - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); - Blockbench.read(CustomTheme.sideloaded_themes, {errorbox: false}, files => { + localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)) + Blockbench.read(CustomTheme.sideloaded_themes, { errorbox: false }, files => { files.forEach(file => { - let id = file.name.replace(/\.\w+$/, ''); - let theme = new CustomTheme().parseBBTheme(file.content as string); - theme.id = id; - if (!theme.name) theme.name = theme.id; - theme.source = 'file'; - theme.path = file.path; - CustomTheme.themes.push(theme); + let id = file.name.replace(/\.\w+$/, '') + let theme = new CustomTheme().parseBBTheme(file.content as string) + theme.id = id + if (!theme.name) theme.name = theme.id + theme.source = 'file' + theme.path = file.path + CustomTheme.themes.push(theme) }) }) } } catch (err) {} - CustomTheme.loadThumbnailStyles(); + CustomTheme.loadThumbnailStyles() } export function loadThemes() { - let stored_theme_data: ThemeData | undefined; + let stored_theme_data: ThemeData | undefined try { if (localStorage.getItem('theme')) { stored_theme_data = JSON.parse(localStorage.getItem('theme')) @@ -844,73 +875,85 @@ export function loadThemes() { } catch (err) {} if (stored_theme_data) { - let stored_theme = new CustomTheme(stored_theme_data); + let stored_theme = new CustomTheme(stored_theme_data) // Check for updates if (stored_theme.source == 'repository' && stored_theme.id) { - CustomTheme.loadTheme(stored_theme); - fetch(`https://cdn.jsdelivr.net/gh/JannisX11/blockbench-themes/themes/${stored_theme.id}.bbtheme`).then(async (result) => { - let text_content = await result.text(); - if (!text_content) return; - let theme = new CustomTheme().parseBBTheme(text_content); - - if ((theme.version && !stored_theme.version) || (theme.version && stored_theme.version && compareVersions(theme.version, stored_theme.version))) { + CustomTheme.loadTheme(stored_theme) + fetch( + `https://cdn.jsdelivr.net/gh/JannisX11/blockbench-themes/themes/${stored_theme.id}.bbtheme` + ).then(async result => { + let text_content = await result.text() + if (!text_content) return + let theme = new CustomTheme().parseBBTheme(text_content) + + if ( + (theme.version && !stored_theme.version) || + (theme.version && + stored_theme.version && + compareVersions(theme.version, stored_theme.version)) + ) { // Update theme - stored_theme.extend(theme); - stored_theme.source = 'repository'; - stored_theme.load(); - console.log(`Updated Theme "${stored_theme.id}" to v${theme.version}`); + stored_theme.extend(theme) + stored_theme.source = 'repository' + stored_theme.load() + console.log(`Updated Theme "${stored_theme.id}" to v${theme.version}`) } - }); - } else if ((stored_theme.source == 'built_in' || stored_theme.source == 'file') && stored_theme.id) { - let match = CustomTheme.themes.find(t => t.id == stored_theme.id); + }) + } else if ( + (stored_theme.source == 'built_in' || stored_theme.source == 'file') && + stored_theme.id + ) { + let match = CustomTheme.themes.find(t => t.id == stored_theme.id) if (match) { - match.option_values = stored_theme.option_values; - match.load(); + match.option_values = stored_theme.option_values + match.load() } - } else if (stored_theme.source == 'custom') { - CustomTheme.loadTheme(stored_theme); + CustomTheme.loadTheme(stored_theme) } } } -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('theme_window', { name: tl('dialog.settings.theme') + '...', icon: 'style', category: 'blockbench', click: function () { - CustomTheme.dialog.show(); - } + CustomTheme.dialog.show() + }, }) new Action('import_theme', { icon: 'folder', category: 'blockbench', click: function () { - Blockbench.import({ - resource_id: 'config', - extensions: ['bbtheme'], - type: 'Blockbench Theme' - }, function(files) { - CustomTheme.import(files[0]); - }) - } + Blockbench.import( + { + resource_id: 'config', + extensions: ['bbtheme'], + type: 'Blockbench Theme', + }, + function (files) { + CustomTheme.import(files[0]) + } + ) + }, }) new Action('export_theme', { icon: 'style', category: 'blockbench', click: function () { - let id = CustomTheme.selected.id || ''; - let content = CustomTheme.selected.compileBBTheme(); + let id = CustomTheme.selected.id || '' + let content = CustomTheme.selected.compileBBTheme() Blockbench.export({ resource_id: 'config', type: 'Blockbench Theme', extensions: ['bbtheme'], name: id, - content + content, }) - } + }, }) BarItems.import_theme.toElement('#layout_title_bar') BarItems.export_theme.toElement('#layout_title_bar') @@ -918,4 +961,4 @@ BARS.defineActions(function() { Object.assign(window, { CustomTheme, -}); +}) diff --git a/js/io/format.ts b/js/io/format.ts index 2bcc4c4cc..1c6ad4256 100644 --- a/js/io/format.ts +++ b/js/io/format.ts @@ -1,14 +1,14 @@ -import { Vue } from "../lib/libs"; -import { Blockbench } from "../api"; -import { setProjectTitle } from "../interface/interface"; -import { settings, Settings } from "../interface/settings"; -import { TickUpdates } from "../misc"; -import { Mode, Modes } from "../modes"; -import { Group } from "../outliner/group"; -import { Canvas } from "../preview/canvas"; -import { DefaultCameraPresets } from "../preview/preview"; -import { Property } from "../util/property"; -import { SplineMesh } from "../outliner/spline_mesh"; +import { Vue } from '../lib/libs' +import { Blockbench } from '../api' +import { setProjectTitle } from '../interface/interface' +import { settings, Settings } from '../interface/settings' +import { TickUpdates } from '../misc' +import { Mode, Modes } from '../modes' +import { Group } from '../outliner/group' +import { Canvas } from '../preview/canvas' +import { DefaultCameraPresets } from '../preview/preview' +import { Property } from '../util/property' +import { SplineMesh } from '../outliner/spline_mesh' interface FormatPage { component?: Vue.Component @@ -60,12 +60,12 @@ interface CubeSizeLimiter { */ declare const Format: ModelFormat -export const Formats = {}; +export const Formats = {} Object.defineProperty(window, 'Format', { get() { - return Blockbench.Format; - } + return Blockbench.Format + }, }) //Formats @@ -292,8 +292,6 @@ export class ModelFormat implements FormatOptions { onFormatPage?(): void onStart?(): void onSetup?(project: ModelProject, newModel?: boolean): void - - cube_size_limiter?: CubeSizeLimiter @@ -305,137 +303,136 @@ export class ModelFormat implements FormatOptions { constructor(id: string, data: Partial) { if (typeof id == 'object') { - data = id; - id = data.id; + data = id + id = data.id } - Formats[id] = this; - this.id = id; - this.name = data.name || tl('format.'+this.id); - this.description = data.description || tl('format.'+this.id+'.desc'); - if (this.description == 'format.'+this.id+'.desc') this.description = ''; - this.category = data.category || 'other'; - this.target = data.target; - this.show_on_start_screen = true; - this.confidential = false; - this.can_convert_to = true; + Formats[id] = this + this.id = id + this.name = data.name || tl('format.' + this.id) + this.description = data.description || tl('format.' + this.id + '.desc') + if (this.description == 'format.' + this.id + '.desc') this.description = '' + this.category = data.category || 'other' + this.target = data.target + this.show_on_start_screen = true + this.confidential = false + this.can_convert_to = true for (let id in ModelFormat.properties) { - ModelFormat.properties[id].reset(this); + ModelFormat.properties[id].reset(this) } - this.render_sides = data.render_sides; - this.cube_size_limiter = data.cube_size_limiter; - - this.codec = data.codec; - this.onSetup = data.onSetup; - this.onFormatPage = data.onFormatPage; - this.onActivation = data.onActivation; - this.onDeactivation = data.onDeactivation; - this.format_page = data.format_page; - Merge.string(this, data, 'icon'); - Merge.boolean(this, data, 'show_on_start_screen'); - Merge.boolean(this, data, 'show_in_new_list'); - Merge.boolean(this, data, 'can_convert_to'); - Merge.boolean(this, data, 'confidential'); - - if (data.new) this.new = data.new; + this.render_sides = data.render_sides + this.cube_size_limiter = data.cube_size_limiter + + this.codec = data.codec + this.onSetup = data.onSetup + this.onFormatPage = data.onFormatPage + this.onActivation = data.onActivation + this.onDeactivation = data.onDeactivation + this.format_page = data.format_page + Merge.string(this, data, 'icon') + Merge.boolean(this, data, 'show_on_start_screen') + Merge.boolean(this, data, 'show_in_new_list') + Merge.boolean(this, data, 'can_convert_to') + Merge.boolean(this, data, 'confidential') + + if (data.new) this.new = data.new if (data.rotation_limit && data.rotation_snap === undefined) { - data.rotation_snap = true; + data.rotation_snap = true } for (let id in ModelFormat.properties) { - ModelFormat.properties[id].merge(this, data); + ModelFormat.properties[id].merge(this, data) } if (this.format_page && this.format_page.component) { Vue.component(`format_page_${this.id}`, this.format_page.component) } - Blockbench.dispatchEvent('construct_format', {format: this}); + Blockbench.dispatchEvent('construct_format', { format: this }) } select() { if (Format && typeof Format.onDeactivation == 'function') { Format.onDeactivation() } // @ts-ignore Incompatible internal and external types - Blockbench.Format = Blockbench.Project.format = this; + Blockbench.Format = Blockbench.Project.format = this if (typeof this.onActivation == 'function') { Format.onActivation() } Canvas.buildGrid() if (Format.centered_grid) { - scene.position.set(0, 0, 0); - Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 8; + scene.position.set(0, 0, 0) + Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 8 } else { - scene.position.set(-8, 0, -8); - Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 0; + scene.position.set(-8, 0, -8) + Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 0 } PreviewModel.getActiveModels().forEach(model => { - model.update(); + model.update() }) - Settings.updateSettingsInProfiles(); + Settings.updateSettingsInProfiles() Preview.all.forEach(preview => { if (preview.isOrtho && typeof preview.angle == 'number') { // @ts-ignore Incompatible internal and external types - preview.loadAnglePreset(DefaultCameraPresets[preview.angle+1] as AnglePreset) + preview.loadAnglePreset(DefaultCameraPresets[preview.angle + 1] as AnglePreset) } }) if (Mode.selected && !Condition(Mode.selected.condition)) { - (this.pose_mode ? Modes.options.paint : Modes.options.edit).select(); + ;(this.pose_mode ? Modes.options.paint : Modes.options.edit).select() } - Interface.Panels.animations.inside_vue.$data.animation_files_enabled = this.animation_files; + Interface.Panels.animations.inside_vue.$data.animation_files_enabled = this.animation_files // @ts-ignore - Interface.status_bar.vue.Format = this; - UVEditor.vue.cube_uv_rotation = this.uv_rotation; - if (Modes.vue) Modes.vue.$forceUpdate(); - TickUpdates.interface = true; - Canvas.updateShading(); + Interface.status_bar.vue.Format = this + UVEditor.vue.cube_uv_rotation = this.uv_rotation + if (Modes.vue) Modes.vue.$forceUpdate() + TickUpdates.interface = true + Canvas.updateShading() Canvas.updateRenderSides() - Blockbench.dispatchEvent('select_format', {format: this, project: Project}); - return this; + Blockbench.dispatchEvent('select_format', { format: this, project: Project }) + return this } new(): boolean { // @ts-ignore Conflicting internal and external types if (newProject(this)) { - (BarItems.project_window as Action).click(); - return true; + ;(BarItems.project_window as Action).click() + return true } - return false; + return false } convertTo() { + Undo.history.empty() + Undo.index = 0 + Project.export_path = '' + Project.unhandled_root_fields = {} - Undo.history.empty(); - Undo.index = 0; - Project.export_path = ''; - Project.unhandled_root_fields = {}; - - var old_format = Blockbench.Format as ModelFormat; - this.select(); + var old_format = Blockbench.Format as ModelFormat + this.select() Modes.options.edit.select() // Box UV if (!this.optional_box_uv) { - Project.box_uv = this.box_uv; + Project.box_uv = this.box_uv Cube.all.forEach(cube => { - cube.setUVMode(this.box_uv); + cube.setUVMode(this.box_uv) }) } if (!this.per_texture_uv_size && old_format.per_texture_uv_size) { - let tex = Texture.getDefault(); + let tex = Texture.getDefault() if (tex) { - Project.texture_width = tex.uv_width; - Project.texture_height = tex.uv_height; + Project.texture_width = tex.uv_width + Project.texture_height = tex.uv_height } } if (this.per_texture_uv_size && !old_format.per_texture_uv_size) { Texture.all.forEach(tex => { - tex.uv_width = Project.texture_width; - tex.uv_height = Project.texture_height; + tex.uv_width = Project.texture_width + tex.uv_height = Project.texture_height }) } //Bone Rig if (!this.bone_rig && old_format.bone_rig) { Group.all.forEach(group => { - group.rotation.V3_set(0, 0, 0); + group.rotation.V3_set(0, 0, 0) }) } if (this.bone_rig && !old_format.bone_rig) { @@ -454,35 +451,37 @@ export class ModelFormat implements FormatOptions { // @ts-ignore if (!Project.geometry_name && Project.name) { // @ts-ignore - Project.geometry_name = Project.name; + Project.geometry_name = Project.name } } if (this.bone_rig) { Group.all.forEach(group => { - group.createUniqueName(); + group.createUniqueName() }) } if (this.centered_grid != old_format.centered_grid) { - let offset = this.centered_grid ? -8 : 8; + let offset = this.centered_grid ? -8 : 8 Cube.all.forEach(cube => { for (let axis of [0, 2]) { - cube.from[axis] += offset; - cube.to[axis] += offset; - cube.origin[axis] += offset; + cube.from[axis] += offset + cube.to[axis] += offset + cube.origin[axis] += offset } }) Group.all.forEach(group => { - group.origin[0] += offset; - group.origin[2] += offset; + group.origin[0] += offset + group.origin[2] += offset }) } if (!this.single_texture && old_format.single_texture && Texture.all.length) { - let texture = Texture.getDefault(); - Outliner.elements.filter((el: OutlinerElement) => 'applyTexture' in el).forEach(el => { - // @ts-ignore - el.applyTexture(texture, true) - }) + let texture = Texture.getDefault() + Outliner.elements + .filter((el: OutlinerElement) => 'applyTexture' in el) + .forEach(el => { + // @ts-ignore + el.applyTexture(texture, true) + }) } //Rotate Cubes @@ -529,12 +528,16 @@ export class ModelFormat implements FormatOptions { } //Canvas Limit - if (this.cube_size_limiter && !old_format.cube_size_limiter && !settings.deactivate_size_limit.value) { + if ( + this.cube_size_limiter && + !old_format.cube_size_limiter && + !settings.deactivate_size_limit.value + ) { Cube.all.forEach(cube => { - this.cube_size_limiter.move(cube); + this.cube_size_limiter.move(cube) }) Cube.all.forEach(cube => { - this.cube_size_limiter.clamp(cube); + this.cube_size_limiter.clamp(cube) }) } @@ -542,24 +545,26 @@ export class ModelFormat implements FormatOptions { if (this.rotation_limit && !old_format.rotation_limit && this.rotate_cubes) { Cube.all.forEach(cube => { if (!cube.rotation.allEqual(0)) { - var axis = (getAxisNumber(cube.rotationAxis())) || 0; - var cube_rotation = this.rotation_snap ? Math.round(cube.rotation[axis]/22.5)*22.5 : cube.rotation[axis]; - var angle = limitNumber( cube_rotation, -45, 45 ); + var axis = getAxisNumber(cube.rotationAxis()) || 0 + var cube_rotation = this.rotation_snap + ? Math.round(cube.rotation[axis] / 22.5) * 22.5 + : cube.rotation[axis] + var angle = limitNumber(cube_rotation, -45, 45) cube.rotation.V3_set(0, 0, 0) - cube.rotation[axis] = angle; + cube.rotation[axis] = angle } }) } //Animation Mode if (!this.animation_mode && old_format.animation_mode) { - Animator.animations.length = 0; + Animator.animations.length = 0 } - Project.saved = false; - setProjectTitle(); + Project.saved = false + setProjectTitle() - Blockbench.dispatchEvent('convert_format', {format: this, old_format}) + Blockbench.dispatchEvent('convert_format', { format: this, old_format }) if (typeof this.onSetup == 'function') { this.onSetup(Project) @@ -571,59 +576,58 @@ export class ModelFormat implements FormatOptions { updateSelection() } delete() { - delete Formats[this.id]; + delete Formats[this.id] // @ts-ignore - if (this.codec && this.codec.format == this) delete this.codec.format; - Blockbench.dispatchEvent('delete_format', {format: this}); + if (this.codec && this.codec.format == this) delete this.codec.format + Blockbench.dispatchEvent('delete_format', { format: this }) } } -new Property(ModelFormat, 'string', 'node_name_regex'); -new Property(ModelFormat, 'boolean', 'box_uv'); -new Property(ModelFormat, 'boolean', 'optional_box_uv'); -new Property(ModelFormat, 'boolean', 'box_uv_float_size'); -new Property(ModelFormat, 'boolean', 'single_texture'); -new Property(ModelFormat, 'boolean', 'single_texture_default'); -new Property(ModelFormat, 'boolean', 'per_group_texture'); -new Property(ModelFormat, 'boolean', 'per_texture_uv_size'); -new Property(ModelFormat, 'boolean', 'model_identifier', {default: true}); -new Property(ModelFormat, 'boolean', 'legacy_editable_file_name'); -new Property(ModelFormat, 'boolean', 'parent_model_id'); -new Property(ModelFormat, 'boolean', 'vertex_color_ambient_occlusion'); -new Property(ModelFormat, 'boolean', 'animated_textures'); -new Property(ModelFormat, 'boolean', 'bone_rig'); -new Property(ModelFormat, 'boolean', 'armature_rig'); -new Property(ModelFormat, 'boolean', 'centered_grid'); -new Property(ModelFormat, 'boolean', 'rotate_cubes'); -new Property(ModelFormat, 'boolean', 'stretch_cubes'); -new Property(ModelFormat, 'boolean', 'integer_size'); -new Property(ModelFormat, 'boolean', 'meshes'); -new Property(ModelFormat, 'boolean', 'splines'); -new Property(ModelFormat, 'boolean', 'texture_meshes'); -new Property(ModelFormat, 'boolean', 'billboards'); -new Property(ModelFormat, 'boolean', 'locators'); -new Property(ModelFormat, 'boolean', 'rotation_limit'); -new Property(ModelFormat, 'boolean', 'rotation_snap'); -new Property(ModelFormat, 'boolean', 'uv_rotation'); -new Property(ModelFormat, 'boolean', 'java_cube_shading_properties'); -new Property(ModelFormat, 'boolean', 'java_face_properties'); -new Property(ModelFormat, 'boolean', 'cullfaces'); -new Property(ModelFormat, 'boolean', 'select_texture_for_particles'); -new Property(ModelFormat, 'boolean', 'texture_mcmeta'); -new Property(ModelFormat, 'boolean', 'bone_binding_expression'); -new Property(ModelFormat, 'boolean', 'animation_files'); -new Property(ModelFormat, 'boolean', 'animation_controllers'); -new Property(ModelFormat, 'boolean', 'image_editor'); -new Property(ModelFormat, 'boolean', 'edit_mode', {default: true}); -new Property(ModelFormat, 'boolean', 'paint_mode', {default: true}); -new Property(ModelFormat, 'boolean', 'pose_mode'); -new Property(ModelFormat, 'boolean', 'display_mode'); -new Property(ModelFormat, 'boolean', 'animation_mode'); -new Property(ModelFormat, 'boolean', 'texture_folder'); -new Property(ModelFormat, 'boolean', 'pbr'); - +new Property(ModelFormat, 'string', 'node_name_regex') +new Property(ModelFormat, 'boolean', 'box_uv') +new Property(ModelFormat, 'boolean', 'optional_box_uv') +new Property(ModelFormat, 'boolean', 'box_uv_float_size') +new Property(ModelFormat, 'boolean', 'single_texture') +new Property(ModelFormat, 'boolean', 'single_texture_default') +new Property(ModelFormat, 'boolean', 'per_group_texture') +new Property(ModelFormat, 'boolean', 'per_texture_uv_size') +new Property(ModelFormat, 'boolean', 'model_identifier', { default: true }) +new Property(ModelFormat, 'boolean', 'legacy_editable_file_name') +new Property(ModelFormat, 'boolean', 'parent_model_id') +new Property(ModelFormat, 'boolean', 'vertex_color_ambient_occlusion') +new Property(ModelFormat, 'boolean', 'animated_textures') +new Property(ModelFormat, 'boolean', 'bone_rig') +new Property(ModelFormat, 'boolean', 'armature_rig') +new Property(ModelFormat, 'boolean', 'centered_grid') +new Property(ModelFormat, 'boolean', 'rotate_cubes') +new Property(ModelFormat, 'boolean', 'stretch_cubes') +new Property(ModelFormat, 'boolean', 'integer_size') +new Property(ModelFormat, 'boolean', 'meshes') +new Property(ModelFormat, 'boolean', 'splines') +new Property(ModelFormat, 'boolean', 'texture_meshes') +new Property(ModelFormat, 'boolean', 'billboards') +new Property(ModelFormat, 'boolean', 'locators') +new Property(ModelFormat, 'boolean', 'rotation_limit') +new Property(ModelFormat, 'boolean', 'rotation_snap') +new Property(ModelFormat, 'boolean', 'uv_rotation') +new Property(ModelFormat, 'boolean', 'java_cube_shading_properties') +new Property(ModelFormat, 'boolean', 'java_face_properties') +new Property(ModelFormat, 'boolean', 'cullfaces') +new Property(ModelFormat, 'boolean', 'select_texture_for_particles') +new Property(ModelFormat, 'boolean', 'texture_mcmeta') +new Property(ModelFormat, 'boolean', 'bone_binding_expression') +new Property(ModelFormat, 'boolean', 'animation_files') +new Property(ModelFormat, 'boolean', 'animation_controllers') +new Property(ModelFormat, 'boolean', 'image_editor') +new Property(ModelFormat, 'boolean', 'edit_mode', { default: true }) +new Property(ModelFormat, 'boolean', 'paint_mode', { default: true }) +new Property(ModelFormat, 'boolean', 'pose_mode') +new Property(ModelFormat, 'boolean', 'display_mode') +new Property(ModelFormat, 'boolean', 'animation_mode') +new Property(ModelFormat, 'boolean', 'texture_folder') +new Property(ModelFormat, 'boolean', 'pbr') Object.assign(window, { ModelFormat, - Formats -}); + Formats, +}) diff --git a/js/io/formats/fbx.ts b/js/io/formats/fbx.ts index d7bf6505d..14391de73 100644 --- a/js/io/formats/fbx.ts +++ b/js/io/formats/fbx.ts @@ -1,31 +1,31 @@ -import { Blockbench } from "../../api"; -import { Filesystem } from "../../file_system"; -import { Armature } from "../../outliner/armature"; -import { adjustFromAndToForInflateAndStretch } from "../../outliner/cube"; -import { patchedAtob } from "../../util/util"; +import { Blockbench } from '../../api' +import { Filesystem } from '../../file_system' +import { Armature } from '../../outliner/armature' +import { adjustFromAndToForInflateAndStretch } from '../../outliner/cube' +import { patchedAtob } from '../../util/util' import { JSZip, THREE } from './../../lib/libs' -const _FBX_VERSION = 7300; +const _FBX_VERSION = 7300 -type TNumType = 'I'|'D'|'F'|'L'|'C'|'Y'; +type TNumType = 'I' | 'D' | 'F' | 'L' | 'C' | 'Y' type TNumVal = { - type: TNumType, - value: number | BigInt, - isTNum: true, + type: TNumType + value: number | BigInt + isTNum: true toString: () => string } /** * Wraps a number to include the type - * @param {TNumType} type - * @param {number} value + * @param {TNumType} type + * @param {number} value */ function TNum(type: TNumType, value: number | BigInt): TNumVal { return { type, value, isTNum: true, - toString: () => 'key' + value.toString() - }; + toString: () => 'key' + value.toString(), + } } function printAttributeList(list, type = 'i', key = 'a') { @@ -33,76 +33,80 @@ function printAttributeList(list, type = 'i', key = 'a') { _values: [`_*${list.length}`], _type: type, [key]: list, - } + } } type FBXNode = { _key?: string - _values: (string|number|TNumVal)[] + _values: (string | number | TNumVal)[] [key: string]: any } - var codec = new Codec('fbx', { name: 'FBX Model', extension: 'fbx', support_partial_export: true, compile(options) { - options = Object.assign(this.getExportOptions(), options); - let scope = this; - let export_scale = (options.scale||16) / 100; - let model = []; - let armature_scale_const = new THREE.Vector3(1, 1, 1); - model.push([ - '; FBX 7.3.0 project file', - '; Created by the Blockbench FBX Exporter', - '; ----------------------------------------------------', - '; ', - '', - '', - ].join('\n')); + options = Object.assign(this.getExportOptions(), options) + let scope = this + let export_scale = (options.scale || 16) / 100 + let model = [] + let armature_scale_const = new THREE.Vector3(1, 1, 1) + model.push( + [ + '; FBX 7.3.0 project file', + '; Created by the Blockbench FBX Exporter', + '; ----------------------------------------------------', + '; ', + '', + '', + ].join('\n') + ) function formatFBXComment(comment) { - return '\n; ' + comment.split(/\n/g).join('\n; ') - + '\n;------------------------------------------------------------------\n\n'; + return ( + '\n; ' + + comment.split(/\n/g).join('\n; ') + + '\n;------------------------------------------------------------------\n\n' + ) } - let UUIDMap = {}; + let UUIDMap = {} function getID(uuid: string | number): TNumVal { - if (uuid == 0) return TNum('L', 0); - if (UUIDMap[uuid]) return UUIDMap[uuid]; - let string_array = []; + if (uuid == 0) return TNum('L', 0) + if (UUIDMap[uuid]) return UUIDMap[uuid] + let string_array = [] for (let i = 0; i < 8; i++) { - string_array.push(Math.floor(Math.random()*10)); + string_array.push(Math.floor(Math.random() * 10)) } - string_array[0] = '7'; - let s = string_array.join(''); - UUIDMap[uuid] = TNum('L', parseInt(s)); - return UUIDMap[uuid]; + string_array[0] = '7' + let s = string_array.join('') + UUIDMap[uuid] = TNum('L', parseInt(s)) + return UUIDMap[uuid] } - let UniqueNames = {}; + let UniqueNames = {} function getUniqueName(namespace: string, uuid: string, original_name: string): string { - if (!UniqueNames[namespace]) UniqueNames[namespace] = {}; - let names = UniqueNames[namespace]; - if (names[uuid]) return names[uuid]; + if (!UniqueNames[namespace]) UniqueNames[namespace] = {} + let names = UniqueNames[namespace] + if (names[uuid]) return names[uuid] - let existing_names = Object.values(names); + let existing_names = Object.values(names) if (!existing_names.includes(original_name)) { - names[uuid] = original_name; - return names[uuid]; + names[uuid] = original_name + return names[uuid] } - let i = 1; + let i = 1 while (existing_names.includes(original_name + '_' + i)) { - i++; + i++ } - names[uuid] = original_name + '_' + i; - return names[uuid]; + names[uuid] = original_name + '_' + i + return names[uuid] } // FBXHeaderExtension - let date = new Date(); - let dateString = date.toISOString().replace('T', ' ').replace('.', ':').replace('Z', ''); - let model_url = 'C:\\Users\\Blockbench\\foobar.fbx'; + let date = new Date() + let dateString = date.toISOString().replace('T', ' ').replace('.', ':').replace('Z', '') + let model_url = 'C:\\Users\\Blockbench\\foobar.fbx' model.push({ FBXHeaderExtension: { FBXHeaderVersion: 1003, @@ -111,46 +115,130 @@ var codec = new Codec('fbx', { CreationTimeStamp: { Version: 1000, Year: date.getFullYear(), - Month: date.getMonth()+1, + Month: date.getMonth() + 1, Day: date.getDate(), Hour: date.getHours(), Minute: date.getMinutes(), Second: date.getSeconds(), - Millisecond: date.getMilliseconds() + Millisecond: date.getMilliseconds(), }, Creator: 'Blockbench ' + Blockbench.version, SceneInfo: { - _values: ['SceneInfo::GlobalInfo', "UserData"], - Type: "UserData", + _values: ['SceneInfo::GlobalInfo', 'UserData'], + Type: 'UserData', Version: 100, - MetaData: { + MetaData: { Version: 100, - Title: "", - Subject: "", - Author: "", - Keywords: "", - Revision: "", - Comment: "" + Title: '', + Subject: '', + Author: '', + Keywords: '', + Revision: '', + Comment: '', + }, + Properties70: { + P01: { + _key: 'P', + _values: ['DocumentUrl', 'KString', 'Url', '', model_url], + }, + P02: { + _key: 'P', + _values: ['SrcDocumentUrl', 'KString', 'Url', '', model_url], + }, + P03: { _key: 'P', _values: ['Original', 'Compound', '', ''] }, + P04: { + _key: 'P', + _values: [ + 'Original|ApplicationVendor', + 'KString', + '', + '', + 'Blockbench', + ], + }, + P05: { + _key: 'P', + _values: [ + 'Original|ApplicationName', + 'KString', + '', + '', + 'Blockbench FBX Exporter', + ], + }, + P06: { + _key: 'P', + _values: [ + 'Original|ApplicationVersion', + 'KString', + '', + '', + Blockbench.version, + ], + }, + P07: { + _key: 'P', + _values: [ + 'Original|DateTime_GMT', + 'DateTime', + '', + '', + '01/01/1970 00:00:00.000', + ], + }, + P08: { + _key: 'P', + _values: ['Original|FileName', 'KString', '', '', '/foobar.fbx'], + }, + P09: { _key: 'P', _values: ['LastSaved', 'Compound', '', ''] }, + P10: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationVendor', + 'KString', + '', + '', + 'Blockbench', + ], + }, + P11: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationName', + 'KString', + '', + '', + 'Blockbench FBX Exporter', + ], + }, + P12: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationVersion', + 'KString', + '', + '', + Blockbench.version, + ], + }, + P13: { + _key: 'P', + _values: [ + 'LastSaved|DateTime_GMT', + 'DateTime', + '', + '', + '01/01/1970 00:00:00.000', + ], + }, + P14: { + _key: 'P', + _values: ['Original|ApplicationNativeFile', 'KString', '', '', ''], + }, }, - Properties70: { - P01: {_key: 'P', _values: ["DocumentUrl", "KString", "Url", "", model_url]}, - P02: {_key: 'P', _values: ["SrcDocumentUrl", "KString", "Url", "", model_url]}, - P03: {_key: 'P', _values: ["Original", "Compound", "", ""]}, - P04: {_key: 'P', _values: ["Original|ApplicationVendor", "KString", "", "", "Blockbench"]}, - P05: {_key: 'P', _values: ["Original|ApplicationName", "KString", "", "", "Blockbench FBX Exporter"]}, - P06: {_key: 'P', _values: ["Original|ApplicationVersion", "KString", "", "", Blockbench.version]}, - P07: {_key: 'P', _values: ["Original|DateTime_GMT", "DateTime", "", "", "01/01/1970 00:00:00.000"]}, - P08: {_key: 'P', _values: ["Original|FileName", "KString", "", "", "/foobar.fbx"]}, - P09: {_key: 'P', _values: ["LastSaved", "Compound", "", ""]}, - P10: {_key: 'P', _values: ["LastSaved|ApplicationVendor", "KString", "", "", "Blockbench"]}, - P11: {_key: 'P', _values: ["LastSaved|ApplicationName", "KString", "", "", "Blockbench FBX Exporter"]}, - P12: {_key: 'P', _values: ["LastSaved|ApplicationVersion", "KString", "", "", Blockbench.version]}, - P13: {_key: 'P', _values: ["LastSaved|DateTime_GMT", "DateTime", "", "", "01/01/1970 00:00:00.000"]}, - P14: {_key: 'P', _values: ["Original|ApplicationNativeFile", "KString", "", "", ""]}, - } }, }, - FileId: "iVFoobar", + FileId: 'iVFoobar', CreationTime: dateString, Creator: Settings.get('credit'), }) @@ -159,51 +247,78 @@ var codec = new Codec('fbx', { GlobalSettings: { Version: 1000, Properties70: { - P01: {_key: 'P', _values: ["UpAxis", "int", "Integer", "",1]}, - P02: {_key: 'P', _values: ["UpAxisSign", "int", "Integer", "",1]}, - P03: {_key: 'P', _values: ["FrontAxis", "int", "Integer", "",2]}, - P04: {_key: 'P', _values: ["FrontAxisSign", "int", "Integer", "",1]}, - P05: {_key: 'P', _values: ["CoordAxis", "int", "Integer", "",0]}, - P08: {_key: 'P', _values: ["CoordAxisSign", "int", "Integer", "",1]}, - P09: {_key: 'P', _values: ["OriginalUpAxis", "int", "Integer", "",-1]}, - P10: {_key: 'P', _values: ["OriginalUpAxisSign", "int", "Integer", "",1]}, - P11: {_key: 'P', _values: ["UnitScaleFactor", "double", "Number", "",TNum('D', 1)]}, - P12: {_key: 'P', _values: ["OriginalUnitScaleFactor", "double", "Number", "",TNum('D', 1)]}, - P13: {_key: 'P', _values: ["AmbientColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P14: {_key: 'P', _values: ["DefaultCamera", "KString", "", "", "Producer Perspective"]}, - P15: {_key: 'P', _values: ["TimeMode", "enum", "", "",0]}, - P16: {_key: 'P', _values: ["TimeSpanStart", "KTime", "Time", "",TNum('L', 0)]}, - P17: {_key: 'P', _values: ["TimeSpanStop", "KTime", "Time", "",TNum('L', 46186158000)]}, - P18: {_key: 'P', _values: ["CustomFrameRate", "double", "Number", "",TNum('D', 24)]}, - } - } - }); + P01: { _key: 'P', _values: ['UpAxis', 'int', 'Integer', '', 1] }, + P02: { _key: 'P', _values: ['UpAxisSign', 'int', 'Integer', '', 1] }, + P03: { _key: 'P', _values: ['FrontAxis', 'int', 'Integer', '', 2] }, + P04: { _key: 'P', _values: ['FrontAxisSign', 'int', 'Integer', '', 1] }, + P05: { _key: 'P', _values: ['CoordAxis', 'int', 'Integer', '', 0] }, + P08: { _key: 'P', _values: ['CoordAxisSign', 'int', 'Integer', '', 1] }, + P09: { _key: 'P', _values: ['OriginalUpAxis', 'int', 'Integer', '', -1] }, + P10: { _key: 'P', _values: ['OriginalUpAxisSign', 'int', 'Integer', '', 1] }, + P11: { + _key: 'P', + _values: ['UnitScaleFactor', 'double', 'Number', '', TNum('D', 1)], + }, + P12: { + _key: 'P', + _values: ['OriginalUnitScaleFactor', 'double', 'Number', '', TNum('D', 1)], + }, + P13: { + _key: 'P', + _values: [ + 'AmbientColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P14: { + _key: 'P', + _values: ['DefaultCamera', 'KString', '', '', 'Producer Perspective'], + }, + P15: { _key: 'P', _values: ['TimeMode', 'enum', '', '', 0] }, + P16: { + _key: 'P', + _values: ['TimeSpanStart', 'KTime', 'Time', '', TNum('L', 0)], + }, + P17: { + _key: 'P', + _values: ['TimeSpanStop', 'KTime', 'Time', '', TNum('L', 46186158000)], + }, + P18: { + _key: 'P', + _values: ['CustomFrameRate', 'double', 'Number', '', TNum('D', 24)], + }, + }, + }, + }) // Documents Description - model.push(formatFBXComment('Documents Description')); - const uuid = BigInt(Math.floor(Math.random() * 2147483647) + 1); + model.push(formatFBXComment('Documents Description')) + const uuid = BigInt(Math.floor(Math.random() * 2147483647) + 1) model.push({ Documents: { Count: 1, Document: { _values: [TNum('L', uuid)], - Scene: "Scene", + Scene: 'Scene', Properties70: { - P01: {_key: 'P', _values: ["SourceObject", "object", "", ""]}, - P02: {_key: 'P', _values: ["ActiveAnimStackName", "KString", "", "", ""]} + P01: { _key: 'P', _values: ['SourceObject', 'object', '', ''] }, + P02: { _key: 'P', _values: ['ActiveAnimStackName', 'KString', '', '', ''] }, }, - RootNode: 0 - } + RootNode: 0, + }, }, }) // Document References - model.push(formatFBXComment('Document References')); + model.push(formatFBXComment('Document References')) model.push({ - References: { - } - }); - + References: {}, + }) let DefinitionCounter = { node_attributes: 0, @@ -218,118 +333,172 @@ var codec = new Codec('fbx', { animation_layer: 0, animation_curve_node: 0, animation_curve: 0, - }; - let Objects: Record = {}; + } + let Objects: Record = {} let Connections: { - name: string[], - id: (string|TNumVal)[] + name: string[] + id: (string | TNumVal)[] property?: string - }[] = []; + }[] = [] let Takes = { - Current: '' - }; - let root = {name: 'RootNode', uuid: 0}; + Current: '', + } + let root = { name: 'RootNode', uuid: 0 } function getElementPos(element) { - let arr = element.origin.slice(); + let arr = element.origin.slice() if (element.parent instanceof Group) { - arr.V3_subtract(element.parent.origin); + arr.V3_subtract(element.parent.origin) } - return arr.V3_divide(export_scale); + return arr.V3_divide(export_scale) } function addNodeBase(node, fbx_type) { - let unique_name = getUniqueName('object', node.uuid, node.name); - let rotation_order = node.mesh.rotation.order == 'XYZ' ? 5 : 0; - Objects['key'+node.uuid] = { + let unique_name = getUniqueName('object', node.uuid, node.name) + let rotation_order = node.mesh.rotation.order == 'XYZ' ? 5 : 0 + Objects['key' + node.uuid] = { _key: 'Model', _values: [getID(node.uuid), `Model::${unique_name}`, fbx_type], Version: 232, Properties70: { - P1: {_key: 'P', _values: ["RotationActive", "bool", "", "",TNum('I', 1)]}, - P2: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P3: {_key: 'P', _values: ["ScalingMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...getElementPos(node).map(v => TNum('D', v))]}, - P5: node.rotation ? {_key: 'P', _values: ["RotationPivot", "Vector3D", "Vector", "", TNum('D',0), TNum('D',0), TNum('D',0)]} : undefined, - P6: node.rotation ? {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...node.rotation.map(v => TNum('D', v))]} : undefined, - P7: node.rotation ? {_key: 'P', _values: ["RotationOrder", "enum", "", "", rotation_order]} : undefined, - P8: node.faces ? {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]} : undefined, + P1: { _key: 'P', _values: ['RotationActive', 'bool', '', '', TNum('I', 1)] }, + P2: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P3: { + _key: 'P', + _values: [ + 'ScalingMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...getElementPos(node).map(v => TNum('D', v)), + ], + }, + P5: node.rotation + ? { + _key: 'P', + _values: [ + 'RotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + } + : undefined, + P6: node.rotation + ? { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...node.rotation.map(v => TNum('D', v)), + ], + } + : undefined, + P7: node.rotation + ? { _key: 'P', _values: ['RotationOrder', 'enum', '', '', rotation_order] } + : undefined, + P8: node.faces + ? { _key: 'P', _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0] } + : undefined, }, Shading: true, - Culling: "CullingOff", - }; - let parent = node.parent == 'root' ? root : node.parent; + Culling: 'CullingOff', + } + let parent = node.parent == 'root' ? root : node.parent Connections.push({ - name: [`Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`], + name: [ + `Model::${unique_name}`, + `Model::${getUniqueName('object', parent.uuid, parent.name)}`, + ], id: [getID(node.uuid), getID(parent.uuid)], - }); - DefinitionCounter.model++; - return Objects['key'+node.uuid]; + }) + DefinitionCounter.model++ + return Objects['key' + node.uuid] } // Groups Group.all.forEach(group => { - if (!group.export) return; - addNodeBase(group, 'Null'); - }); + if (!group.export) return + addNodeBase(group, 'Null') + }) // Locators + Null Objects - [...Locator.all, ...NullObject.all].forEach(group => { - if (!group.export) return; - addNodeBase(group, 'Null'); - }); + ;[...Locator.all, ...NullObject.all].forEach(group => { + if (!group.export) return + addNodeBase(group, 'Null') + }) // Meshes Mesh.all.forEach(mesh => { - if (!mesh.export) return; - addNodeBase(mesh, 'Mesh'); - let unique_name = getUniqueName('object', mesh.uuid, mesh.name); + if (!mesh.export) return + addNodeBase(mesh, 'Mesh') + let unique_name = getUniqueName('object', mesh.uuid, mesh.name) // Geometry - let positions = []; - let normals = []; - let uv = []; - let vertex_keys = []; - let indices = []; - let mesh_normals = mesh.calculateNormals(); + let positions = [] + let normals = [] + let uv = [] + let vertex_keys = [] + let indices = [] + let mesh_normals = mesh.calculateNormals() function addPosition(x, y, z) { - positions.push(x/export_scale, y/export_scale, z/export_scale); + positions.push(x / export_scale, y / export_scale, z / export_scale) } for (let vkey in mesh.vertices) { - addPosition(...mesh.vertices[vkey]); - vertex_keys.push(vkey); + addPosition(...mesh.vertices[vkey]) + vertex_keys.push(vkey) if (mesh.smooth_shading == true) { - normals.push(...mesh_normals[vkey]); + normals.push(...mesh_normals[vkey]) } } - let textures = []; + let textures = [] for (let key in mesh.faces) { if (mesh.faces[key].vertices.length >= 3) { - let face = mesh.faces[key]; - let vertices = face.getSortedVertices(); - let tex = mesh.faces[key].getTexture(); - textures.push(tex); + let face = mesh.faces[key] + let vertices = face.getSortedVertices() + let tex = mesh.faces[key].getTexture() + textures.push(tex) vertices.forEach(vkey => { - uv.push(face.uv[vkey][0] / Project.getUVWidth(tex), 1 - face.uv[vkey][1] / Project.getUVHeight(tex)); + uv.push( + face.uv[vkey][0] / Project.getUVWidth(tex), + 1 - face.uv[vkey][1] / Project.getUVHeight(tex) + ) }) if (mesh.smooth_shading == false) { - normals.push(...face.getNormal(true)); + normals.push(...face.getNormal(true)) } - + vertices.forEach((vkey, vi) => { - let index = vertex_keys.indexOf(vkey); - if (vi+1 == vertices.length) index = -1 -index; - indices.push(index); + let index = vertex_keys.indexOf(vkey) + if (vi + 1 == vertices.length) index = -1 - index + indices.push(index) }) } } - DefinitionCounter.geometry++; + DefinitionCounter.geometry++ - let used_textures = Texture.all.filter(t => textures.includes(t)); + let used_textures = Texture.all.filter(t => textures.includes(t)) let geo_id = getID(mesh.uuid + '_geo') let geometry: FBXNode = { @@ -339,83 +508,86 @@ var codec = new Codec('fbx', { Vertices: { _values: [`_*${positions.length}`], _type: 'd', - a: positions + a: positions, }, PolygonVertexIndex: { _values: [`_*${indices.length}`], _type: 'i', - a: indices + a: indices, }, GeometryVersion: 124, LayerElementNormal: { _values: [0], Version: 101, - Name: "", - MappingInformationType: mesh.smooth_shading ? "ByVertex" : "ByPolygon", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: mesh.smooth_shading ? 'ByVertex' : 'ByPolygon', + ReferenceInformationType: 'Direct', Normals: { _values: [`_*${normals.length}`], _type: 'd', - a: normals - } + a: normals, + }, }, LayerElementUV: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygonVertex", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygonVertex', + ReferenceInformationType: 'Direct', UV: { _values: [`_*${uv.length}`], _type: 'd', - a: uv - } - }, - LayerElementMaterial: used_textures.length <= 1 ? { - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "AllSame", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*1`], - _type: 'i', - a: 0 - }, - } : { - // Multitexture - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*${textures.length}`], - _type: 'i', - a: textures.map(t => used_textures.indexOf(t)) + a: uv, }, }, + LayerElementMaterial: + used_textures.length <= 1 + ? { + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'AllSame', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*1`], + _type: 'i', + a: 0, + }, + } + : { + // Multitexture + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*${textures.length}`], + _type: 'i', + a: textures.map(t => used_textures.indexOf(t)), + }, + }, Layer: { _values: [0], Version: 100, LayerElement1: { _key: 'LayerElement', - Type: "LayerElementNormal", - TypedIndex: 0 + Type: 'LayerElementNormal', + TypedIndex: 0, }, LayerElement2: { _key: 'LayerElement', - Type: "LayerElementMaterial", - TypedIndex: 0 + Type: 'LayerElementMaterial', + TypedIndex: 0, }, LayerElement3: { _key: 'LayerElement', - Type: "LayerElementUV", - TypedIndex: 0 + Type: 'LayerElementUV', + TypedIndex: 0, }, - } - }; - Objects[geo_id.toString()] = geometry; + }, + } + Objects[geo_id.toString()] = geometry Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], @@ -423,244 +595,343 @@ var codec = new Codec('fbx', { }) used_textures.forEach(tex => { Connections.push({ - name: [`Material::${getUniqueName('material', tex.uuid, tex.name)}`, `Model::${unique_name}`], - id: [getID(tex.uuid+'_m'), getID(mesh.uuid)], + name: [ + `Material::${getUniqueName('material', tex.uuid, tex.name)}`, + `Model::${unique_name}`, + ], + id: [getID(tex.uuid + '_m'), getID(mesh.uuid)], }) }) }) Armature.all.forEach(armature => { - let mesh = Mesh.all.find(m => m.getArmature() == armature); - let armature_name = getUniqueName('armature', armature.uuid, armature.name); - DefinitionCounter.pose++; + let mesh = Mesh.all.find(m => m.getArmature() == armature) + let armature_name = getUniqueName('armature', armature.uuid, armature.name) + DefinitionCounter.pose++ // Armature - let armature_attribute_id = getID(armature.uuid + '_attribute'); + let armature_attribute_id = getID(armature.uuid + '_attribute') Objects[armature_attribute_id.toString()] = { _key: 'NodeAttribute', - _values: [armature_attribute_id, `NodeAttribute::${armature_name}`, 'Null'], - TypeFlags: "Null" - }; - DefinitionCounter.node_attributes++; + _values: [armature_attribute_id, `NodeAttribute::${armature_name}`, 'Null'], + TypeFlags: 'Null', + } + DefinitionCounter.node_attributes++ - let armature_id = getID(armature.uuid); + let armature_id = getID(armature.uuid) Objects[armature_id.toString()] = { _key: 'Model', - _values: [armature_id, `Model::${armature_name}`, 'Null'], + _values: [armature_id, `Model::${armature_name}`, 'Null'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...[0, 0, 0].map(v => TNum('D', v))]}, - P5: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...[0, 0, 0].map(v => TNum('D', v))]}, - P6: {_key: 'P', _values: ["Lcl Scaling", "Lcl Scaling", "", "A", ...[1, 1, 1].map(v => TNum('D', v))]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { _key: 'P', _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0] }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...[0, 0, 0].map(v => TNum('D', v)), + ], + }, + P5: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...[0, 0, 0].map(v => TNum('D', v)), + ], + }, + P6: { + _key: 'P', + _values: [ + 'Lcl Scaling', + 'Lcl Scaling', + '', + 'A', + ...[1, 1, 1].map(v => TNum('D', v)), + ], + }, }, - Culling: "CullingOff" - }; - let parent = armature.parent == 'root' ? root : armature.parent; + Culling: 'CullingOff', + } + let parent = armature.parent == 'root' ? root : armature.parent Connections.push({ - name: [`Model::${armature_name}`, `Model::${getUniqueName('object', parent.uuid as string, parent.name)}`], + name: [ + `Model::${armature_name}`, + `Model::${getUniqueName('object', parent.uuid as string, parent.name)}`, + ], id: [getID(armature.uuid), getID(parent.uuid)], - }); - DefinitionCounter.model++; + }) + DefinitionCounter.model++ Connections.push({ - name: [`NodeAttribute::${armature_name}`, `Model::${armature_name}`], + name: [`NodeAttribute::${armature_name}`, `Model::${armature_name}`], id: [armature_attribute_id, armature_id], - }); - + }) // Bind pose - let pose: FBXNode; + let pose: FBXNode if (mesh) { - let pose_id = getID(mesh.uuid + '_bind_pose'); + let pose_id = getID(mesh.uuid + '_bind_pose') pose = { _key: 'Pose', - _values: [pose_id, `Pose::${getUniqueName('object', mesh.uuid, mesh.name)}`, 'BindPose'], - Type: "BindPose", + _values: [ + pose_id, + `Pose::${getUniqueName('object', mesh.uuid, mesh.name)}`, + 'BindPose', + ], + Type: 'BindPose', Version: 100, NbPoseNodes: 0, } - // Mesh bind pose - let matrix = new THREE.Matrix4(); - matrix.scale(armature_scale_const); + let matrix = new THREE.Matrix4() + matrix.scale(armature_scale_const) pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(mesh.uuid), - Matrix: printAttributeList(matrix.elements, 'd') + Matrix: printAttributeList(matrix.elements, 'd'), } - pose.NbPoseNodes++; + pose.NbPoseNodes++ // Armature bind pose - let matrix2 = new THREE.Matrix4(); - matrix.scale(armature_scale_const); + let matrix2 = new THREE.Matrix4() + matrix.scale(armature_scale_const) pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(armature.uuid), - Matrix: printAttributeList(matrix2.elements, 'd') + Matrix: printAttributeList(matrix2.elements, 'd'), } - pose.NbPoseNodes++; + pose.NbPoseNodes++ - Objects[pose_id.toString()] = pose; - DefinitionCounter.pose++; + Objects[pose_id.toString()] = pose + DefinitionCounter.pose++ } // Bones - const bone_list = []; - const bind_matrix_list = []; + const bone_list = [] + const bind_matrix_list = [] function processBone(bone) { console.log(bone) - bone_list.push(bone); - let unique_name = getUniqueName('bone', bone.uuid, bone.name); - let attribute_id = getID(bone.uuid + '_attribute'); + bone_list.push(bone) + let unique_name = getUniqueName('bone', bone.uuid, bone.name) + let attribute_id = getID(bone.uuid + '_attribute') Objects[attribute_id.toString()] = { _key: 'NodeAttribute', - _values: [attribute_id, `NodeAttribute::${unique_name}`, 'LimbNode'], - Properties70: { - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', bone.length / export_scale)]}, + _values: [attribute_id, `NodeAttribute::${unique_name}`, 'LimbNode'], + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Size', + 'double', + 'Number', + '', + TNum('D', bone.length / export_scale), + ], + }, }, - TypeFlags: "Skeleton" - }; - DefinitionCounter.node_attributes++; - - let object_id = getID(bone.uuid); + TypeFlags: 'Skeleton', + } + DefinitionCounter.node_attributes++ + + let object_id = getID(bone.uuid) Objects[object_id.toString()] = { _key: 'Model', - _values: [object_id, `Model::${unique_name}`, 'LimbNode'], + _values: [object_id, `Model::${unique_name}`, 'LimbNode'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...getElementPos(bone).map(v => TNum('D', v))]}, - P5: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...bone.rotation.map(v => TNum('D', v))]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { + _key: 'P', + _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...getElementPos(bone).map(v => TNum('D', v)), + ], + }, + P5: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...bone.rotation.map(v => TNum('D', v)), + ], + }, //P4: {_key: 'P', _values: ["RotationOrder", "enum", "", "", rotation_order]}, }, - Culling: "CullingOff" - }; - let parent = bone.parent == 'root' ? root : bone.parent; + Culling: 'CullingOff', + } + let parent = bone.parent == 'root' ? root : bone.parent Connections.push({ - name: [`Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`], + name: [ + `Model::${unique_name}`, + `Model::${getUniqueName('object', parent.uuid, parent.name)}`, + ], id: [getID(bone.uuid), getID(parent.uuid)], - }); - DefinitionCounter.model++; + }) + DefinitionCounter.model++ Connections.push({ - name: [`NodeAttribute::${unique_name}`, `Model::${unique_name}`], + name: [`NodeAttribute::${unique_name}`, `Model::${unique_name}`], id: [attribute_id, object_id], - }); - - + }) if (bone.children.length == 0) { // End bone node - let attribute_id_end = getID(bone.uuid + '_end_attribute'); + let attribute_id_end = getID(bone.uuid + '_end_attribute') Objects[attribute_id_end.toString()] = { _key: 'NodeAttribute', - _values: [attribute_id_end, `NodeAttribute::${unique_name}_end`, 'LimbNode'], - Properties70: { - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', bone.length * 10)]}, + _values: [ + attribute_id_end, + `NodeAttribute::${unique_name}_end`, + 'LimbNode', + ], + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Size', + 'double', + 'Number', + '', + TNum('D', bone.length * 10), + ], + }, }, - TypeFlags: "Skeleton" - }; - DefinitionCounter.node_attributes++; - - let object_id_end = getID(bone.uuid+'_end'); + TypeFlags: 'Skeleton', + } + DefinitionCounter.node_attributes++ + + let object_id_end = getID(bone.uuid + '_end') Objects[object_id_end.toString()] = { _key: 'Model', - _values: [object_id_end, `Model::${unique_name}_end`, 'LimbNode'], + _values: [object_id_end, `Model::${unique_name}_end`, 'LimbNode'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", 0, bone.length / export_scale, 0]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { + _key: 'P', + _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + 0, + bone.length / export_scale, + 0, + ], + }, }, - Culling: "CullingOff" - }; + Culling: 'CullingOff', + } Connections.push({ name: [`Model::${unique_name}_end`, `Model::${unique_name}`], id: [object_id_end, getID(bone.uuid)], - }); - DefinitionCounter.model++; + }) + DefinitionCounter.model++ Connections.push({ name: [`NodeAttribute::${unique_name}_end`, `Model::${unique_name}_end`], id: [attribute_id_end, object_id_end], - }); + }) } // Bind pose if (mesh) { - let matrix = new THREE.Matrix4().copy(bone.mesh.inverse_bind_matrix).invert(); - matrix.scale(armature_scale_const); - pose['PoseNode'+object_id] = { + let matrix = new THREE.Matrix4().copy(bone.mesh.inverse_bind_matrix).invert() + matrix.scale(armature_scale_const) + pose['PoseNode' + object_id] = { _key: 'PoseNode', Node: object_id, - Matrix: printAttributeList(matrix.elements, 'd') + Matrix: printAttributeList(matrix.elements, 'd'), } - pose.NbPoseNodes++; - bind_matrix_list.push(matrix); + pose.NbPoseNodes++ + bind_matrix_list.push(matrix) } // Children for (let child of bone.children) { - processBone(child); + processBone(child) } } for (let child of armature.children) { - processBone(child); + processBone(child) } // Mesh deformers if (mesh) { - let deformer_id = getID(armature.uuid+'_deformer'); + let deformer_id = getID(armature.uuid + '_deformer') Objects[deformer_id.toString()] = { _key: 'Deformer', _values: [deformer_id, `Deformer::${armature_name}`, 'Skin'], Version: 101, - Link_DeformAcuracy: 50 - }; - DefinitionCounter.deformer++; + Link_DeformAcuracy: 50, + } + DefinitionCounter.deformer++ Connections.push({ - name: [`Deformer::${armature_name}`, `Geometry::${getUniqueName('object', mesh.uuid, mesh.name)}`], + name: [ + `Deformer::${armature_name}`, + `Geometry::${getUniqueName('object', mesh.uuid, mesh.name)}`, + ], id: [deformer_id, getID(mesh.uuid + '_geo')], - }); + }) - let mesh_transform = new THREE.Matrix4().copy(mesh.mesh.matrixWorld); - let vertex_keys = Object.keys(mesh.vertices); + let mesh_transform = new THREE.Matrix4().copy(mesh.mesh.matrixWorld) + let vertex_keys = Object.keys(mesh.vertices) for (let bone of bone_list) { - let sub_deformer_id = getID(bone.uuid+'_deformer'); - let bone_name = getUniqueName('bone', bone.uuid, bone.name); - let indices = []; - let weights = []; + let sub_deformer_id = getID(bone.uuid + '_deformer') + let bone_name = getUniqueName('bone', bone.uuid, bone.name) + let indices = [] + let weights = [] for (let vkey in bone.vertex_weights) { if (bone.vertex_weights[vkey] > 0.001) { - indices.push(vertex_keys.indexOf(vkey)); - weights.push(Math.clamp(bone.vertex_weights[vkey], 0, 1)); + indices.push(vertex_keys.indexOf(vkey)) + weights.push(Math.clamp(bone.vertex_weights[vkey], 0, 1)) } } - let bind_matrix = bind_matrix_list[bone_list.indexOf(bone)]; + let bind_matrix = bind_matrix_list[bone_list.indexOf(bone)] Objects[sub_deformer_id.toString()] = { _key: 'Deformer', _values: [sub_deformer_id, `SubDeformer::${bone_name}`, 'Cluster'], Version: 100, - UserData: ["", ""], + UserData: ['', ''], Indexes: printAttributeList(indices, 'i'), Weights: printAttributeList(weights, 'd'), Transform: printAttributeList(mesh_transform.elements, 'd'), TransformLink: printAttributeList(bind_matrix.elements, 'd'), - }; - DefinitionCounter.deformer++; + } + DefinitionCounter.deformer++ Connections.push({ name: [`SubDeformer::${bone_name}`, `Deformer::${armature_name}`], id: [sub_deformer_id, deformer_id], - }); + }) Connections.push({ - name: [`Model::${getUniqueName('bone', bone.uuid, bone.name)}`, `SubDeformer::${bone_name}`], + name: [ + `Model::${getUniqueName('bone', bone.uuid, bone.name)}`, + `SubDeformer::${bone_name}`, + ], id: [getID(bone.uuid), sub_deformer_id], - }); + }) } } }) @@ -675,76 +946,100 @@ var codec = new Codec('fbx', { down: [0, -1, 0], } Cube.all.forEach(cube => { - if (!cube.export) return; - addNodeBase(cube, 'Mesh'); - let unique_name = getUniqueName('object', cube.uuid, cube.name); + if (!cube.export) return + addNodeBase(cube, 'Mesh') + let unique_name = getUniqueName('object', cube.uuid, cube.name) // Geometry - let positions = []; - let normals = []; - let uv = []; - let indices = []; + let positions = [] + let normals = [] + let uv = [] + let indices = [] function addPosition(x, y, z) { positions.push( (x - cube.origin[0]) / export_scale, (y - cube.origin[1]) / export_scale, (z - cube.origin[2]) / export_scale - ); + ) } - var adjustedFrom = cube.from.slice(); - var adjustedTo = cube.to.slice(); - adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, cube); + var adjustedFrom = cube.from.slice() + var adjustedTo = cube.to.slice() + adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, cube) - addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2] ); - addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]); - addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2] ); - addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2] ); - addPosition(adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2] ); + addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2]) + addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]) + addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2]) + addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]) + addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]) + addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2]) + addPosition(adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]) + addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2]) - let textures = []; + let textures = [] for (let fkey in cube.faces) { - let face = cube.faces[fkey]; - if (face.texture === null) continue; - let texture = face.getTexture(); - textures.push(texture); - normals.push(...cube_face_normals[fkey]); + let face = cube.faces[fkey] + if (face.texture === null) continue + let texture = face.getTexture() + textures.push(texture) + normals.push(...cube_face_normals[fkey]) let uv_outputs = [ - [face.uv[0] / Project.getUVWidth(texture), 1 - face.uv[3] / Project.getUVHeight(texture)], - [face.uv[2] / Project.getUVWidth(texture), 1 - face.uv[3] / Project.getUVHeight(texture)], - [face.uv[2] / Project.getUVWidth(texture), 1 - face.uv[1] / Project.getUVHeight(texture)], - [face.uv[0] / Project.getUVWidth(texture), 1 - face.uv[1] / Project.getUVHeight(texture)], - ]; - var rot = face.rotation || 0; + [ + face.uv[0] / Project.getUVWidth(texture), + 1 - face.uv[3] / Project.getUVHeight(texture), + ], + [ + face.uv[2] / Project.getUVWidth(texture), + 1 - face.uv[3] / Project.getUVHeight(texture), + ], + [ + face.uv[2] / Project.getUVWidth(texture), + 1 - face.uv[1] / Project.getUVHeight(texture), + ], + [ + face.uv[0] / Project.getUVWidth(texture), + 1 - face.uv[1] / Project.getUVHeight(texture), + ], + ] + var rot = face.rotation || 0 while (rot > 0) { - uv_outputs.splice(0, 0, uv_outputs.pop()); - rot -= 90; + uv_outputs.splice(0, 0, uv_outputs.pop()) + rot -= 90 } uv_outputs.forEach(coord => { - uv.push(...coord); + uv.push(...coord) }) - let vertices; + let vertices switch (fkey) { - case 'north': vertices = [3, 6, 4, -1-1]; break; - case 'east': vertices = [2, 3, 1, -1-0]; break; - case 'south': vertices = [7, 2, 0, -1-5]; break; - case 'west': vertices = [6, 7, 5, -1-4]; break; - case 'up': vertices = [5, 0, 1, -1-4]; break; - case 'down': vertices = [6, 3, 2, -1-7]; break; + case 'north': + vertices = [3, 6, 4, -1 - 1] + break + case 'east': + vertices = [2, 3, 1, -1 - 0] + break + case 'south': + vertices = [7, 2, 0, -1 - 5] + break + case 'west': + vertices = [6, 7, 5, -1 - 4] + break + case 'up': + vertices = [5, 0, 1, -1 - 4] + break + case 'down': + vertices = [6, 3, 2, -1 - 7] + break } - indices.push(...vertices); + indices.push(...vertices) } - DefinitionCounter.geometry++; + DefinitionCounter.geometry++ - let used_textures = Texture.all.filter(t => textures.includes(t)); + let used_textures = Texture.all.filter(t => textures.includes(t)) let geo_id = getID(cube.uuid + '_geo') let geometry = { @@ -754,83 +1049,86 @@ var codec = new Codec('fbx', { Vertices: { _values: [`_*${positions.length}`], _type: 'd', - a: positions + a: positions, }, PolygonVertexIndex: { _values: [`_*${indices.length}`], _type: 'i', - a: indices + a: indices, }, GeometryVersion: 124, LayerElementNormal: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'Direct', Normals: { _values: [`_*${normals.length}`], _type: 'd', - a: normals - } + a: normals, + }, }, LayerElementUV: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygonVertex", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygonVertex', + ReferenceInformationType: 'Direct', UV: { _values: [`_*${uv.length}`], _type: 'd', - a: uv - } - }, - LayerElementMaterial: used_textures.length <= 1 ? { - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "AllSame", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*1`], - _type: 'i', - a: 0 - }, - } : { - // Multitexture - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*${textures.length}`], - _type: 'i', - a: textures.map(t => used_textures.indexOf(t)) + a: uv, }, }, + LayerElementMaterial: + used_textures.length <= 1 + ? { + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'AllSame', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*1`], + _type: 'i', + a: 0, + }, + } + : { + // Multitexture + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*${textures.length}`], + _type: 'i', + a: textures.map(t => used_textures.indexOf(t)), + }, + }, Layer: { _values: [0], Version: 100, LayerElement1: { _key: 'LayerElement', - Type: "LayerElementNormal", - TypedIndex: 0 + Type: 'LayerElementNormal', + TypedIndex: 0, }, LayerElement2: { _key: 'LayerElement', - Type: "LayerElementMaterial", - TypedIndex: 0 + Type: 'LayerElementMaterial', + TypedIndex: 0, }, LayerElement3: { _key: 'LayerElement', - Type: "LayerElementUV", - TypedIndex: 0 + Type: 'LayerElementUV', + TypedIndex: 0, }, - } - }; - Objects['key'+geo_id] = geometry; + }, + } + Objects['key' + geo_id] = geometry Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], @@ -838,159 +1136,214 @@ var codec = new Codec('fbx', { }) used_textures.forEach(tex => { Connections.push({ - name: [`Material::${getUniqueName('texture', tex.uuid, tex.name)}`, `Model::${unique_name}`], - id: [getID(tex.uuid+'_m'), getID(cube.uuid)], + name: [ + `Material::${getUniqueName('texture', tex.uuid, tex.name)}`, + `Model::${unique_name}`, + ], + id: [getID(tex.uuid + '_m'), getID(cube.uuid)], }) }) }) // Textures Texture.all.forEach(tex => { - DefinitionCounter.material++; - DefinitionCounter.texture++; - DefinitionCounter.image++; + DefinitionCounter.material++ + DefinitionCounter.texture++ + DefinitionCounter.image++ - let fileContent = null; - let fileName = tex.path; - let relativeName = tex.name; + let fileContent = null + let fileName = tex.path + let relativeName = tex.name // If no file path, use embedded texture if (tex.path == '') { - fileName = ''; - relativeName = ''; + fileName = '' + relativeName = '' } if (options.embed_textures || tex.path == '') { - fileContent = tex.getBase64(); + fileContent = tex.getBase64() } - let unique_name = getUniqueName('texture', tex.uuid, tex.name); + let unique_name = getUniqueName('texture', tex.uuid, tex.name) let mat_object = { _key: 'Material', - _values: [getID(tex.uuid+'_m'), `Material::${unique_name}`, ''], + _values: [getID(tex.uuid + '_m'), `Material::${unique_name}`, ''], Version: 102, - ShadingModel: "lambert", + ShadingModel: 'lambert', MultiLayer: 0, - Properties70: { - P2: {_key: 'P', _values: ["Emissive", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P3: {_key: 'P', _values: ["Ambient", "Vector3D", "Vector", "",TNum('D',0.2), TNum('D',0.2), TNum('D',0.2)]}, - P4: {_key: 'P', _values: ["Diffuse", "Vector3D", "Vector", "",TNum('D',0.8), TNum('D',0.8), TNum('D',0.8)]}, - P5: {_key: 'P', _values: ["Opacity", "double", "Number", "",TNum('D', 1)]}, - } - }; + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Emissive', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P3: { + _key: 'P', + _values: [ + 'Ambient', + 'Vector3D', + 'Vector', + '', + TNum('D', 0.2), + TNum('D', 0.2), + TNum('D', 0.2), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Diffuse', + 'Vector3D', + 'Vector', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P5: { _key: 'P', _values: ['Opacity', 'double', 'Number', '', TNum('D', 1)] }, + }, + } let tex_object = { _key: 'Texture', - _values: [getID(tex.uuid+'_t'), `Texture::${unique_name}`, ''], - Type: "TextureVideoClip", + _values: [getID(tex.uuid + '_t'), `Texture::${unique_name}`, ''], + Type: 'TextureVideoClip', Version: 202, TextureName: `Texture::${unique_name}`, Media: `Video::${unique_name}`, FileName: fileName, RelativeFilename: relativeName, - ModelUVTranslation: [TNum('D',0),TNum('D',0)], - ModelUVScaling: [TNum('D',1),TNum('D',1)], - Texture_Alpha_Source: "None", - Cropping: [0,0,0,0], - }; + ModelUVTranslation: [TNum('D', 0), TNum('D', 0)], + ModelUVScaling: [TNum('D', 1), TNum('D', 1)], + Texture_Alpha_Source: 'None', + Cropping: [0, 0, 0, 0], + } let image_object = { _key: 'Video', - _values: [getID(tex.uuid+'_i'), `Video::${unique_name}`, 'Clip'], - Type: "Clip", - Properties70: { - P: ["Path", "KString", "XRefUrl", "", tex.path || tex.name] + _values: [getID(tex.uuid + '_i'), `Video::${unique_name}`, 'Clip'], + Type: 'Clip', + Properties70: { + P: ['Path', 'KString', 'XRefUrl', '', tex.path || tex.name], }, UseMipMap: 0, Filename: fileName, RelativeFilename: relativeName, - Content: fileContent - }; - Objects['key'+tex.uuid+'_m'] = mat_object; - Objects['key'+tex.uuid+'_t'] = tex_object; - Objects['key'+tex.uuid+'_i'] = image_object; + Content: fileContent, + } + Objects['key' + tex.uuid + '_m'] = mat_object + Objects['key' + tex.uuid + '_t'] = tex_object + Objects['key' + tex.uuid + '_i'] = image_object Connections.push({ - name: [`Texture::${unique_name}`, `Material::${unique_name}`], - id: [getID(tex.uuid+'_t'), getID(tex.uuid+'_m')], - property: "DiffuseColor" - }); + name: [`Texture::${unique_name}`, `Material::${unique_name}`], + id: [getID(tex.uuid + '_t'), getID(tex.uuid + '_m')], + property: 'DiffuseColor', + }) Connections.push({ - name: [`Video::${unique_name}`, `Texture::${unique_name}`], - id: [getID(tex.uuid+'_i'), getID(tex.uuid+'_t')], - }); + name: [`Video::${unique_name}`, `Texture::${unique_name}`], + id: [getID(tex.uuid + '_i'), getID(tex.uuid + '_t')], + }) }) // Animations if (options.include_animations) { - let anim_clips = Codecs.gltf.buildAnimationTracks(export_scale, false); // Handles sampling of math based curves etc. - let time_factor = 46186158000; // Arbitrary factor, found in three.js FBX importer + let anim_clips = Codecs.gltf.buildAnimationTracks(export_scale, false) // Handles sampling of math based curves etc. + let time_factor = 46186158000 // Arbitrary factor, found in three.js FBX importer anim_clips.forEach(clip => { - DefinitionCounter.animation_stack++; - DefinitionCounter.animation_layer++; + DefinitionCounter.animation_stack++ + DefinitionCounter.animation_layer++ - let stack_id = getID(clip.uuid+'_s'); - let layer_id = getID(clip.uuid+'_l'); - let unique_name = getUniqueName('animation', clip.uuid, clip.name); - let fbx_duration = Math.round(clip.duration * time_factor); + let stack_id = getID(clip.uuid + '_s') + let layer_id = getID(clip.uuid + '_l') + let unique_name = getUniqueName('animation', clip.uuid, clip.name) + let fbx_duration = Math.round(clip.duration * time_factor) let stack = { _key: 'AnimationStack', _values: [stack_id, `AnimStack::${unique_name}`, ''], Properties70: { - p1: {_key: 'P', _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', fbx_duration)]}, - p2: {_key: 'P', _values: ['ReferenceStop', 'KTime', 'Time', '', TNum('L', fbx_duration)]}, - } - }; + p1: { + _key: 'P', + _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', fbx_duration)], + }, + p2: { + _key: 'P', + _values: [ + 'ReferenceStop', + 'KTime', + 'Time', + '', + TNum('L', fbx_duration), + ], + }, + }, + } let layer = { _key: 'AnimationLayer', _values: [layer_id, `AnimLayer::${unique_name}`, ''], - _force_compound: true - }; - Objects['key'+clip.uuid+'_s'] = stack; - Objects['key'+clip.uuid+'_l'] = layer; + _force_compound: true, + } + Objects['key' + clip.uuid + '_s'] = stack + Objects['key' + clip.uuid + '_l'] = layer Connections.push({ name: [`AnimLayer::${unique_name}`, `AnimStack::${unique_name}`], id: [layer_id, stack_id], - }); + }) clip.tracks.forEach(track => { // Track = CurveNode - DefinitionCounter.animation_curve_node++; + DefinitionCounter.animation_curve_node++ let track_id = getID(clip.uuid + '.' + track.name) - let track_name = `AnimCurveNode::${unique_name}.${track.channel[0].toUpperCase()}`; + let track_name = `AnimCurveNode::${unique_name}.${track.channel[0].toUpperCase()}` let curve_node = { _key: 'AnimationCurveNode', _values: [track_id, track_name, ''], Properties70: { - p1: {_key: 'P', _values: [`d|X`, 'Number', '', 'A', TNum('D',1)]}, - p2: {_key: 'P', _values: [`d|Y`, 'Number', '', 'A', TNum('D',1)]}, - p3: {_key: 'P', _values: [`d|Z`, 'Number', '', 'A', TNum('D',1)]}, - } - }; - let timecodes = track.times.map(second => Math.round(second * time_factor)); - Objects['key'+clip.uuid + '.' + track.name] = curve_node; + p1: { _key: 'P', _values: [`d|X`, 'Number', '', 'A', TNum('D', 1)] }, + p2: { _key: 'P', _values: [`d|Y`, 'Number', '', 'A', TNum('D', 1)] }, + p3: { _key: 'P', _values: [`d|Z`, 'Number', '', 'A', TNum('D', 1)] }, + }, + } + let timecodes = track.times.map(second => Math.round(second * time_factor)) + Objects['key' + clip.uuid + '.' + track.name] = curve_node // Connect to bone Connections.push({ - name: [track_name, `Model::${getUniqueName('object', track.group_uuid, track.name)}`], + name: [ + track_name, + `Model::${getUniqueName('object', track.group_uuid, track.name)}`, + ], id: [track_id, getID(track.group_uuid)], - property: track.channel == 'position' ? "Lcl Translation" : (track.channel == 'rotation' ? "Lcl Rotation" : "Lcl Scaling") - }); + property: + track.channel == 'position' + ? 'Lcl Translation' + : track.channel == 'rotation' + ? 'Lcl Rotation' + : 'Lcl Scaling', + }) // Connect to layer Connections.push({ name: [track_name, `AnimLayer::${unique_name}`], id: [track_id, layer_id], - }); - + }) - ['X', 'Y', 'Z'].forEach((axis_letter, axis_number) => { - DefinitionCounter.animation_curve++; + ;['X', 'Y', 'Z'].forEach((axis_letter, axis_number) => { + DefinitionCounter.animation_curve++ - let curve_id = getID(clip.uuid + '.' + track.name + '.' + axis_letter); - let curve_name = `AnimCurve::${unique_name}.${track.channel[0].toUpperCase()}${axis_letter}`; + let curve_id = getID(clip.uuid + '.' + track.name + '.' + axis_letter) + let curve_name = `AnimCurve::${unique_name}.${track.channel[0].toUpperCase()}${axis_letter}` - let values = track.values.filter((val, i) => (i % 3) == axis_number); + let values = track.values.filter((val, i) => i % 3 == axis_number) if (track.channel == 'rotation') { - values.forEach((v, i) => values[i] = Math.radToDeg(v)); + values.forEach((v, i) => (values[i] = Math.radToDeg(v))) } let curve = { _key: 'AnimationCurve', @@ -1000,38 +1353,38 @@ var codec = new Codec('fbx', { KeyTime: { _values: [`_*${timecodes.length}`], _type: 'd', - a: timecodes + a: timecodes, }, KeyValueFloat: { _values: [`_*${values.length}`], _type: 'f', - a: values + a: values, }, KeyAttrFlags: { _values: [`_*${1}`], _type: 'i', - a: [24836] + a: [24836], }, KeyAttrDataFloat: { _values: [`_*${4}`], _type: 'f', - a: [0,0,255790911,0] + a: [0, 0, 255790911, 0], }, KeyAttrRefCount: { _values: [`_*${1}`], _type: 'i', - a: [timecodes.length] + a: [timecodes.length], }, - }; - Objects['key'+clip.uuid + '.' + track.name + axis_letter] = curve; + } + Objects['key' + clip.uuid + '.' + track.name + axis_letter] = curve // Connect to track Connections.push({ name: [curve_name, track_name], id: [curve_id, track_id], - property: `d|${axis_letter}` - }); - }); + property: `d|${axis_letter}`, + }) + }) }) Takes[clip.uuid] = { @@ -1040,15 +1393,15 @@ var codec = new Codec('fbx', { FileName: `${unique_name}.tak`, LocalTime: [0, fbx_duration], ReferenceTime: [0, fbx_duration], - }; + } }) } // Object definitions - model.push(formatFBXComment('Object definitions')); - let total_definition_count = 1; + model.push(formatFBXComment('Object definitions')) + let total_definition_count = 1 for (let key in DefinitionCounter) { - total_definition_count += DefinitionCounter[key]; + total_definition_count += DefinitionCounter[key] } model.push({ Definitions: { @@ -1057,656 +1410,1598 @@ var codec = new Codec('fbx', { global_settings: { _key: 'ObjectType', _values: ['GlobalSettings'], - Count: 1 + Count: 1, }, - node_attribute: DefinitionCounter.node_attributes ? { - _key: 'ObjectType', - _values: ['NodeAttribute'], - Count: DefinitionCounter.node_attributes, - PropertyTemplate: { - _values: ['FbxNull'], - Properties70: { - P1: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', 100)]}, - P3: {_key: 'P', _values: ["Look", "enum", "", "",1]}, + node_attribute: DefinitionCounter.node_attributes + ? { + _key: 'ObjectType', + _values: ['NodeAttribute'], + Count: DefinitionCounter.node_attributes, + PropertyTemplate: { + _values: ['FbxNull'], + Properties70: { + P1: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P2: { + _key: 'P', + _values: ['Size', 'double', 'Number', '', TNum('D', 100)], + }, + P3: { _key: 'P', _values: ['Look', 'enum', '', '', 1] }, + }, + }, } - } - } : undefined, - model: DefinitionCounter.model ? { - _key: 'ObjectType', - _values: ['Model'], - Count: DefinitionCounter.model, - PropertyTemplate: { - _values: ['FbxNode'], - Properties70: { - P01: {_key: 'P', _values: ["QuaternionInterpolate", "enum", "", "",0]}, - P02: {_key: 'P', _values: ["RotationOffset", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P03: {_key: 'P', _values: ["RotationPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P04: {_key: 'P', _values: ["ScalingOffset", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P05: {_key: 'P', _values: ["ScalingPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P06: {_key: 'P', _values: ["TranslationActive", "bool", "", "",TNum('I', 0)]}, - P07: {_key: 'P', _values: ["TranslationMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P08: {_key: 'P', _values: ["TranslationMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P09: {_key: 'P', _values: ["TranslationMinX", "bool", "", "",TNum('I', 0)]}, - P10: {_key: 'P', _values: ["TranslationMinY", "bool", "", "",TNum('I', 0)]}, - P11: {_key: 'P', _values: ["TranslationMinZ", "bool", "", "",TNum('I', 0)]}, - P12: {_key: 'P', _values: ["TranslationMaxX", "bool", "", "",TNum('I', 0)]}, - P13: {_key: 'P', _values: ["TranslationMaxY", "bool", "", "",TNum('I', 0)]}, - P14: {_key: 'P', _values: ["TranslationMaxZ", "bool", "", "",TNum('I', 0)]}, - P15: {_key: 'P', _values: ["RotationOrder", "enum", "", "",5]}, - P16: {_key: 'P', _values: ["RotationSpaceForLimitOnly", "bool", "", "",TNum('I', 0)]}, - P17: {_key: 'P', _values: ["RotationStiffnessX", "double", "Number", "",TNum('D', 0)]}, - P18: {_key: 'P', _values: ["RotationStiffnessY", "double", "Number", "",TNum('D', 0)]}, - P19: {_key: 'P', _values: ["RotationStiffnessZ", "double", "Number", "",TNum('D', 0)]}, - P20: {_key: 'P', _values: ["AxisLen", "double", "Number", "",TNum('D', 10)]}, - P21: {_key: 'P', _values: ["PreRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P22: {_key: 'P', _values: ["PostRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P23: {_key: 'P', _values: ["RotationActive", "bool", "", "",TNum('I', 0)]}, - P24: {_key: 'P', _values: ["RotationMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P25: {_key: 'P', _values: ["RotationMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P26: {_key: 'P', _values: ["RotationMinX", "bool", "", "",TNum('I', 0)]}, - P27: {_key: 'P', _values: ["RotationMinY", "bool", "", "",TNum('I', 0)]}, - P28: {_key: 'P', _values: ["RotationMinZ", "bool", "", "",TNum('I', 0)]}, - P29: {_key: 'P', _values: ["RotationMaxX", "bool", "", "",TNum('I', 0)]}, - P30: {_key: 'P', _values: ["RotationMaxY", "bool", "", "",TNum('I', 0)]}, - P31: {_key: 'P', _values: ["RotationMaxZ", "bool", "", "",TNum('I', 0)]}, - P32: {_key: 'P', _values: ["InheritType", "enum", "", "",0]}, - P33: {_key: 'P', _values: ["ScalingActive", "bool", "", "",TNum('I', 0)]}, - P34: {_key: 'P', _values: ["ScalingMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P35: {_key: 'P', _values: ["ScalingMax", "Vector3D", "Vector", "",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P36: {_key: 'P', _values: ["ScalingMinX", "bool", "", "",TNum('I', 0)]}, - P37: {_key: 'P', _values: ["ScalingMinY", "bool", "", "",TNum('I', 0)]}, - P38: {_key: 'P', _values: ["ScalingMinZ", "bool", "", "",TNum('I', 0)]}, - P39: {_key: 'P', _values: ["ScalingMaxX", "bool", "", "",TNum('I', 0)]}, - P40: {_key: 'P', _values: ["ScalingMaxY", "bool", "", "",TNum('I', 0)]}, - P41: {_key: 'P', _values: ["ScalingMaxZ", "bool", "", "",TNum('I', 0)]}, - P42: {_key: 'P', _values: ["GeometricTranslation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P43: {_key: 'P', _values: ["GeometricRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P44: {_key: 'P', _values: ["GeometricScaling", "Vector3D", "Vector", "",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P45: {_key: 'P', _values: ["MinDampRangeX", "double", "Number", "",TNum('D', 0)]}, - P46: {_key: 'P', _values: ["MinDampRangeY", "double", "Number", "",TNum('D', 0)]}, - P47: {_key: 'P', _values: ["MinDampRangeZ", "double", "Number", "",TNum('D', 0)]}, - P48: {_key: 'P', _values: ["MaxDampRangeX", "double", "Number", "",TNum('D', 0)]}, - P49: {_key: 'P', _values: ["MaxDampRangeY", "double", "Number", "",TNum('D', 0)]}, - P50: {_key: 'P', _values: ["MaxDampRangeZ", "double", "Number", "",TNum('D', 0)]}, - P51: {_key: 'P', _values: ["MinDampStrengthX", "double", "Number", "",TNum('D', 0)]}, - P52: {_key: 'P', _values: ["MinDampStrengthY", "double", "Number", "",TNum('D', 0)]}, - P53: {_key: 'P', _values: ["MinDampStrengthZ", "double", "Number", "",TNum('D', 0)]}, - P54: {_key: 'P', _values: ["MaxDampStrengthX", "double", "Number", "",TNum('D', 0)]}, - P55: {_key: 'P', _values: ["MaxDampStrengthY", "double", "Number", "",TNum('D', 0)]}, - P56: {_key: 'P', _values: ["MaxDampStrengthZ", "double", "Number", "",TNum('D', 0)]}, - P57: {_key: 'P', _values: ["PreferedAngleX", "double", "Number", "",TNum('D', 0)]}, - P58: {_key: 'P', _values: ["PreferedAngleY", "double", "Number", "",TNum('D', 0)]}, - P59: {_key: 'P', _values: ["PreferedAngleZ", "double", "Number", "",TNum('D', 0)]}, - P60: {_key: 'P', _values: ["LookAtProperty", "object", "", ""]}, - P61: {_key: 'P', _values: ["UpVectorProperty", "object", "", ""]}, - P62: {_key: 'P', _values: ["Show", "bool", "", "",TNum('I', 1)]}, - P63: {_key: 'P', _values: ["NegativePercentShapeSupport", "bool", "", "",TNum('I', 1)]}, - P64: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",-1]}, - P65: {_key: 'P', _values: ["Freeze", "bool", "", "",TNum('I', 0)]}, - P66: {_key: 'P', _values: ["LODBox", "bool", "", "",TNum('I', 0)]}, - P67: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P68: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P69: {_key: 'P', _values: ["Lcl Scaling", "Lcl Scaling", "", "A",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P70: {_key: 'P', _values: ["Visibility", "Visibility", "", "A",TNum('D',1)]}, - P71: {_key: 'P', _values: ["Visibility Inheritance", "Visibility Inheritance", "", "",1]}, + : undefined, + model: DefinitionCounter.model + ? { + _key: 'ObjectType', + _values: ['Model'], + Count: DefinitionCounter.model, + PropertyTemplate: { + _values: ['FbxNode'], + Properties70: { + P01: { + _key: 'P', + _values: ['QuaternionInterpolate', 'enum', '', '', 0], + }, + P02: { + _key: 'P', + _values: [ + 'RotationOffset', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P03: { + _key: 'P', + _values: [ + 'RotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: [ + 'ScalingOffset', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P05: { + _key: 'P', + _values: [ + 'ScalingPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P06: { + _key: 'P', + _values: [ + 'TranslationActive', + 'bool', + '', + '', + TNum('I', 0), + ], + }, + P07: { + _key: 'P', + _values: [ + 'TranslationMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P08: { + _key: 'P', + _values: [ + 'TranslationMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P09: { + _key: 'P', + _values: ['TranslationMinX', 'bool', '', '', TNum('I', 0)], + }, + P10: { + _key: 'P', + _values: ['TranslationMinY', 'bool', '', '', TNum('I', 0)], + }, + P11: { + _key: 'P', + _values: ['TranslationMinZ', 'bool', '', '', TNum('I', 0)], + }, + P12: { + _key: 'P', + _values: ['TranslationMaxX', 'bool', '', '', TNum('I', 0)], + }, + P13: { + _key: 'P', + _values: ['TranslationMaxY', 'bool', '', '', TNum('I', 0)], + }, + P14: { + _key: 'P', + _values: ['TranslationMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P15: { + _key: 'P', + _values: ['RotationOrder', 'enum', '', '', 5], + }, + P16: { + _key: 'P', + _values: [ + 'RotationSpaceForLimitOnly', + 'bool', + '', + '', + TNum('I', 0), + ], + }, + P17: { + _key: 'P', + _values: [ + 'RotationStiffnessX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P18: { + _key: 'P', + _values: [ + 'RotationStiffnessY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P19: { + _key: 'P', + _values: [ + 'RotationStiffnessZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P20: { + _key: 'P', + _values: ['AxisLen', 'double', 'Number', '', TNum('D', 10)], + }, + P21: { + _key: 'P', + _values: [ + 'PreRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P22: { + _key: 'P', + _values: [ + 'PostRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P23: { + _key: 'P', + _values: ['RotationActive', 'bool', '', '', TNum('I', 0)], + }, + P24: { + _key: 'P', + _values: [ + 'RotationMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P25: { + _key: 'P', + _values: [ + 'RotationMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P26: { + _key: 'P', + _values: ['RotationMinX', 'bool', '', '', TNum('I', 0)], + }, + P27: { + _key: 'P', + _values: ['RotationMinY', 'bool', '', '', TNum('I', 0)], + }, + P28: { + _key: 'P', + _values: ['RotationMinZ', 'bool', '', '', TNum('I', 0)], + }, + P29: { + _key: 'P', + _values: ['RotationMaxX', 'bool', '', '', TNum('I', 0)], + }, + P30: { + _key: 'P', + _values: ['RotationMaxY', 'bool', '', '', TNum('I', 0)], + }, + P31: { + _key: 'P', + _values: ['RotationMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P32: { _key: 'P', _values: ['InheritType', 'enum', '', '', 0] }, + P33: { + _key: 'P', + _values: ['ScalingActive', 'bool', '', '', TNum('I', 0)], + }, + P34: { + _key: 'P', + _values: [ + 'ScalingMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P35: { + _key: 'P', + _values: [ + 'ScalingMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P36: { + _key: 'P', + _values: ['ScalingMinX', 'bool', '', '', TNum('I', 0)], + }, + P37: { + _key: 'P', + _values: ['ScalingMinY', 'bool', '', '', TNum('I', 0)], + }, + P38: { + _key: 'P', + _values: ['ScalingMinZ', 'bool', '', '', TNum('I', 0)], + }, + P39: { + _key: 'P', + _values: ['ScalingMaxX', 'bool', '', '', TNum('I', 0)], + }, + P40: { + _key: 'P', + _values: ['ScalingMaxY', 'bool', '', '', TNum('I', 0)], + }, + P41: { + _key: 'P', + _values: ['ScalingMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P42: { + _key: 'P', + _values: [ + 'GeometricTranslation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P43: { + _key: 'P', + _values: [ + 'GeometricRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P44: { + _key: 'P', + _values: [ + 'GeometricScaling', + 'Vector3D', + 'Vector', + '', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P45: { + _key: 'P', + _values: [ + 'MinDampRangeX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P46: { + _key: 'P', + _values: [ + 'MinDampRangeY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P47: { + _key: 'P', + _values: [ + 'MinDampRangeZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P48: { + _key: 'P', + _values: [ + 'MaxDampRangeX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P49: { + _key: 'P', + _values: [ + 'MaxDampRangeY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P50: { + _key: 'P', + _values: [ + 'MaxDampRangeZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P51: { + _key: 'P', + _values: [ + 'MinDampStrengthX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P52: { + _key: 'P', + _values: [ + 'MinDampStrengthY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P53: { + _key: 'P', + _values: [ + 'MinDampStrengthZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P54: { + _key: 'P', + _values: [ + 'MaxDampStrengthX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P55: { + _key: 'P', + _values: [ + 'MaxDampStrengthY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P56: { + _key: 'P', + _values: [ + 'MaxDampStrengthZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P57: { + _key: 'P', + _values: [ + 'PreferedAngleX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P58: { + _key: 'P', + _values: [ + 'PreferedAngleY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P59: { + _key: 'P', + _values: [ + 'PreferedAngleZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P60: { + _key: 'P', + _values: ['LookAtProperty', 'object', '', ''], + }, + P61: { + _key: 'P', + _values: ['UpVectorProperty', 'object', '', ''], + }, + P62: { + _key: 'P', + _values: ['Show', 'bool', '', '', TNum('I', 1)], + }, + P63: { + _key: 'P', + _values: [ + 'NegativePercentShapeSupport', + 'bool', + '', + '', + TNum('I', 1), + ], + }, + P64: { + _key: 'P', + _values: [ + 'DefaultAttributeIndex', + 'int', + 'Integer', + '', + -1, + ], + }, + P65: { + _key: 'P', + _values: ['Freeze', 'bool', '', '', TNum('I', 0)], + }, + P66: { + _key: 'P', + _values: ['LODBox', 'bool', '', '', TNum('I', 0)], + }, + P67: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P68: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P69: { + _key: 'P', + _values: [ + 'Lcl Scaling', + 'Lcl Scaling', + '', + 'A', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P70: { + _key: 'P', + _values: [ + 'Visibility', + 'Visibility', + '', + 'A', + TNum('D', 1), + ], + }, + P71: { + _key: 'P', + _values: [ + 'Visibility Inheritance', + 'Visibility Inheritance', + '', + '', + 1, + ], + }, + }, + }, } - } - } : undefined, - geometry: DefinitionCounter.geometry ? { - _key: 'ObjectType', - _values: ['Geometry'], - Count: DefinitionCounter.geometry, - PropertyTemplate: { - _values: ['FbxMesh'], - Properties70: { - P1: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P2: {_key: 'P', _values: ["BBoxMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P3: {_key: 'P', _values: ["BBoxMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P4: {_key: 'P', _values: ["Primary Visibility", "bool", "", "",TNum('I', 1)]}, - P5: {_key: 'P', _values: ["Casts Shadows", "bool", "", "",TNum('I', 1)]}, - P6: {_key: 'P', _values: ["Receive Shadows", "bool", "", "",TNum('I', 1)]}, + : undefined, + geometry: DefinitionCounter.geometry + ? { + _key: 'ObjectType', + _values: ['Geometry'], + Count: DefinitionCounter.geometry, + PropertyTemplate: { + _values: ['FbxMesh'], + Properties70: { + P1: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P2: { + _key: 'P', + _values: [ + 'BBoxMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P3: { + _key: 'P', + _values: [ + 'BBoxMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Primary Visibility', + 'bool', + '', + '', + TNum('I', 1), + ], + }, + P5: { + _key: 'P', + _values: ['Casts Shadows', 'bool', '', '', TNum('I', 1)], + }, + P6: { + _key: 'P', + _values: ['Receive Shadows', 'bool', '', '', TNum('I', 1)], + }, + }, + }, } - } - } : undefined, - material: DefinitionCounter.material ? { - _key: 'ObjectType', - _values: ['Material'], - Count: DefinitionCounter.material, - PropertyTemplate: { - _values: ['FbxSurfaceLambert'], - Properties70: { - P01: {_key: 'P', _values: ["ShadingModel", "KString", "", "", "Lambert"]}, - P02: {_key: 'P', _values: ["MultiLayer", "bool", "", "",TNum('I', 0)]}, - P03: {_key: 'P', _values: ["EmissiveColor", "Color", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P04: {_key: 'P', _values: ["EmissiveFactor", "Number", "", "A",TNum('D',1)]}, - P05: {_key: 'P', _values: ["AmbientColor", "Color", "", "A",TNum('D',0.2), TNum('D',0.2), TNum('D',0.2)]}, - P06: {_key: 'P', _values: ["AmbientFactor", "Number", "", "A",TNum('D',1)]}, - P07: {_key: 'P', _values: ["DiffuseColor", "Color", "", "A",TNum('D',0.8), TNum('D',0.8), TNum('D',0.8)]}, - P08: {_key: 'P', _values: ["DiffuseFactor", "Number", "", "A",TNum('D',1)]}, - P09: {_key: 'P', _values: ["Bump", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P10: {_key: 'P', _values: ["NormalMap", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P11: {_key: 'P', _values: ["BumpFactor", "double", "Number", "",TNum('D', 1)]}, - P12: {_key: 'P', _values: ["TransparentColor", "Color", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P13: {_key: 'P', _values: ["TransparencyFactor", "Number", "", "A",TNum('D',0)]}, - P14: {_key: 'P', _values: ["DisplacementColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P15: {_key: 'P', _values: ["DisplacementFactor", "double", "Number", "",TNum('D', 1)]}, - P16: {_key: 'P', _values: ["VectorDisplacementColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P17: {_key: 'P', _values: ["VectorDisplacementFactor", "double", "Number", "",TNum('D', 1)]}, + : undefined, + material: DefinitionCounter.material + ? { + _key: 'ObjectType', + _values: ['Material'], + Count: DefinitionCounter.material, + PropertyTemplate: { + _values: ['FbxSurfaceLambert'], + Properties70: { + P01: { + _key: 'P', + _values: ['ShadingModel', 'KString', '', '', 'Lambert'], + }, + P02: { + _key: 'P', + _values: ['MultiLayer', 'bool', '', '', TNum('I', 0)], + }, + P03: { + _key: 'P', + _values: [ + 'EmissiveColor', + 'Color', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: [ + 'EmissiveFactor', + 'Number', + '', + 'A', + TNum('D', 1), + ], + }, + P05: { + _key: 'P', + _values: [ + 'AmbientColor', + 'Color', + '', + 'A', + TNum('D', 0.2), + TNum('D', 0.2), + TNum('D', 0.2), + ], + }, + P06: { + _key: 'P', + _values: ['AmbientFactor', 'Number', '', 'A', TNum('D', 1)], + }, + P07: { + _key: 'P', + _values: [ + 'DiffuseColor', + 'Color', + '', + 'A', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P08: { + _key: 'P', + _values: ['DiffuseFactor', 'Number', '', 'A', TNum('D', 1)], + }, + P09: { + _key: 'P', + _values: [ + 'Bump', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P10: { + _key: 'P', + _values: [ + 'NormalMap', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P11: { + _key: 'P', + _values: [ + 'BumpFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + P12: { + _key: 'P', + _values: [ + 'TransparentColor', + 'Color', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P13: { + _key: 'P', + _values: [ + 'TransparencyFactor', + 'Number', + '', + 'A', + TNum('D', 0), + ], + }, + P14: { + _key: 'P', + _values: [ + 'DisplacementColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P15: { + _key: 'P', + _values: [ + 'DisplacementFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + P16: { + _key: 'P', + _values: [ + 'VectorDisplacementColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P17: { + _key: 'P', + _values: [ + 'VectorDisplacementFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + }, + }, } - } - } : undefined, - texture: DefinitionCounter.texture ? { - _key: 'ObjectType', - _values: ['Texture'], - Count: DefinitionCounter.texture, - PropertyTemplate: { - _values: ['FbxFileTexture'], - Properties70: { - P01: {_key: 'P', _values: ["TextureTypeUse", "enum", "", "",0]}, - P02: {_key: 'P', _values: ["Texture alpha", "Number", "", "A",TNum('D',1)]}, - P03: {_key: 'P', _values: ["CurrentMappingType", "enum", "", "",0]}, - P04: {_key: 'P', _values: ["WrapModeU", "enum", "", "",0]}, - P05: {_key: 'P', _values: ["WrapModeV", "enum", "", "",0]}, - P06: {_key: 'P', _values: ["UVSwap", "bool", "", "",TNum('I', 0)]}, - P07: {_key: 'P', _values: ["PremultiplyAlpha", "bool", "", "",TNum('I', 1)]}, - P08: {_key: 'P', _values: ["Translation", "Vector", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P09: {_key: 'P', _values: ["Rotation", "Vector", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P10: {_key: 'P', _values: ["Scaling", "Vector", "", "A",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P11: {_key: 'P', _values: ["TextureRotationPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P12: {_key: 'P', _values: ["TextureScalingPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P13: {_key: 'P', _values: ["CurrentTextureBlendMode", "enum", "", "",1]}, - P14: {_key: 'P', _values: ["UVSet", "KString", "", "", "default"]}, - P15: {_key: 'P', _values: ["UseMaterial", "bool", "", "",TNum('I', 0)]}, - P16: {_key: 'P', _values: ["UseMipMap", "bool", "", "",TNum('I', 0)]}, + : undefined, + texture: DefinitionCounter.texture + ? { + _key: 'ObjectType', + _values: ['Texture'], + Count: DefinitionCounter.texture, + PropertyTemplate: { + _values: ['FbxFileTexture'], + Properties70: { + P01: { + _key: 'P', + _values: ['TextureTypeUse', 'enum', '', '', 0], + }, + P02: { + _key: 'P', + _values: ['Texture alpha', 'Number', '', 'A', TNum('D', 1)], + }, + P03: { + _key: 'P', + _values: ['CurrentMappingType', 'enum', '', '', 0], + }, + P04: { _key: 'P', _values: ['WrapModeU', 'enum', '', '', 0] }, + P05: { _key: 'P', _values: ['WrapModeV', 'enum', '', '', 0] }, + P06: { + _key: 'P', + _values: ['UVSwap', 'bool', '', '', TNum('I', 0)], + }, + P07: { + _key: 'P', + _values: ['PremultiplyAlpha', 'bool', '', '', TNum('I', 1)], + }, + P08: { + _key: 'P', + _values: [ + 'Translation', + 'Vector', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P09: { + _key: 'P', + _values: [ + 'Rotation', + 'Vector', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P10: { + _key: 'P', + _values: [ + 'Scaling', + 'Vector', + '', + 'A', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P11: { + _key: 'P', + _values: [ + 'TextureRotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P12: { + _key: 'P', + _values: [ + 'TextureScalingPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P13: { + _key: 'P', + _values: ['CurrentTextureBlendMode', 'enum', '', '', 1], + }, + P14: { + _key: 'P', + _values: ['UVSet', 'KString', '', '', 'default'], + }, + P15: { + _key: 'P', + _values: ['UseMaterial', 'bool', '', '', TNum('I', 0)], + }, + P16: { + _key: 'P', + _values: ['UseMipMap', 'bool', '', '', TNum('I', 0)], + }, + }, + }, } - } - } : undefined, - image: DefinitionCounter.image ? { - _key: 'ObjectType', - _values: ['Video'], - Count: DefinitionCounter.image, - PropertyTemplate: { - _values: ['FbxVideo'], - Properties70: { - P01: {_key: 'P', _values: ["ImageSequence", "bool", "", "",TNum('I', 0)]}, - P02: {_key: 'P', _values: ["ImageSequenceOffset", "int", "Integer", "",0]}, - P03: {_key: 'P', _values: ["FrameRate", "double", "Number", "",TNum('D', 0)]}, - P04: {_key: 'P', _values: ["LastFrame", "int", "Integer", "",0]}, - P05: {_key: 'P', _values: ["Width", "int", "Integer", "",0]}, - P06: {_key: 'P', _values: ["Height", "int", "Integer", "",0]}, - P07: {_key: 'P', _values: ["Path", "KString", "XRefUrl", "", ""]}, - P08: {_key: 'P', _values: ["StartFrame", "int", "Integer", "",0]}, - P09: {_key: 'P', _values: ["StopFrame", "int", "Integer", "",0]}, - P10: {_key: 'P', _values: ["PlaySpeed", "double", "Number", "",TNum('D', 0)]}, - P11: {_key: 'P', _values: ["Offset", "KTime", "Time", "",TNum('L', 0)]}, - P12: {_key: 'P', _values: ["InterlaceMode", "enum", "", "",0]}, - P13: {_key: 'P', _values: ["FreeRunning", "bool", "", "",TNum('I', 0)]}, - P14: {_key: 'P', _values: ["Loop", "bool", "", "",TNum('I', 0)]}, - P15: {_key: 'P', _values: ["AccessMode", "enum", "", "",0]}, + : undefined, + image: DefinitionCounter.image + ? { + _key: 'ObjectType', + _values: ['Video'], + Count: DefinitionCounter.image, + PropertyTemplate: { + _values: ['FbxVideo'], + Properties70: { + P01: { + _key: 'P', + _values: ['ImageSequence', 'bool', '', '', TNum('I', 0)], + }, + P02: { + _key: 'P', + _values: ['ImageSequenceOffset', 'int', 'Integer', '', 0], + }, + P03: { + _key: 'P', + _values: [ + 'FrameRate', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: ['LastFrame', 'int', 'Integer', '', 0], + }, + P05: { _key: 'P', _values: ['Width', 'int', 'Integer', '', 0] }, + P06: { + _key: 'P', + _values: ['Height', 'int', 'Integer', '', 0], + }, + P07: { + _key: 'P', + _values: ['Path', 'KString', 'XRefUrl', '', ''], + }, + P08: { + _key: 'P', + _values: ['StartFrame', 'int', 'Integer', '', 0], + }, + P09: { + _key: 'P', + _values: ['StopFrame', 'int', 'Integer', '', 0], + }, + P10: { + _key: 'P', + _values: [ + 'PlaySpeed', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P11: { + _key: 'P', + _values: ['Offset', 'KTime', 'Time', '', TNum('L', 0)], + }, + P12: { + _key: 'P', + _values: ['InterlaceMode', 'enum', '', '', 0], + }, + P13: { + _key: 'P', + _values: ['FreeRunning', 'bool', '', '', TNum('I', 0)], + }, + P14: { + _key: 'P', + _values: ['Loop', 'bool', '', '', TNum('I', 0)], + }, + P15: { _key: 'P', _values: ['AccessMode', 'enum', '', '', 0] }, + }, + }, } - } - } : undefined, - + : undefined, - pose: DefinitionCounter.pose ? { - _key: 'ObjectType', - _values: ['Pose'], - Count: DefinitionCounter.pose, - } : undefined, - deformer: DefinitionCounter.deformer ? { - _key: 'ObjectType', - _values: ['Deformer'], - Count: DefinitionCounter.deformer, - } : undefined, - - animation_stack: DefinitionCounter.animation_stack ? { - _key: 'ObjectType', - _values: ['AnimationStack'], - Count: DefinitionCounter.animation_stack, - PropertyTemplate: { - _values: ['FbxAnimStack'], - Properties70: { - P01: {_key: 'P', _values: ["Description", "KString", "", "", ""]}, - P02: {_key: 'P', _values: ["LocalStart", "KTime", "Time", "",TNum('L', 0)]}, - P03: {_key: 'P', _values: ["LocalStop", "KTime", "Time", "",TNum('L', 0)]}, - P04: {_key: 'P', _values: ["ReferenceStart", "KTime", "Time", "",TNum('L', 0)]}, - P05: {_key: 'P', _values: ["ReferenceStop", "KTime", "Time", "",TNum('L', 0)]}, + pose: DefinitionCounter.pose + ? { + _key: 'ObjectType', + _values: ['Pose'], + Count: DefinitionCounter.pose, } - } - } : undefined, - animation_layer: DefinitionCounter.animation_layer ? { - _key: 'ObjectType', - _values: ['AnimationLayer'], - Count: DefinitionCounter.animation_layer, - PropertyTemplate: { - _values: ['FbxAnimLayer'], - Properties70: { - P01: {_key: 'P', _values: ["Weight", "Number", "", "A",TNum('D',100)]}, - P02: {_key: 'P', _values: ["Mute", "bool", "", "",TNum('I', 0)]}, - P03: {_key: 'P', _values: ["Solo", "bool", "", "",TNum('I', 0)]}, - P04: {_key: 'P', _values: ["Lock", "bool", "", "",TNum('I', 0)]}, - P05: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P06: {_key: 'P', _values: ["BlendMode", "enum", "", "",0]}, - P07: {_key: 'P', _values: ["RotationAccumulationMode", "enum", "", "",0]}, - P08: {_key: 'P', _values: ["ScaleAccumulationMode", "enum", "", "",0]}, - P09: {_key: 'P', _values: ["BlendModeBypass", "ULongLong", "", "",0]}, + : undefined, + deformer: DefinitionCounter.deformer + ? { + _key: 'ObjectType', + _values: ['Deformer'], + Count: DefinitionCounter.deformer, } - } - } : undefined, - animation_curve_node: DefinitionCounter.animation_curve_node ? { - _key: 'ObjectType', - _values: ['AnimationCurveNode'], - Count: DefinitionCounter.animation_curve_node, - PropertyTemplate: { - _values: ['FbxAnimCurveNode'], - Properties70: { - P01: {_key: 'P', _values: ["d", "Compound", "", ""]}, + : undefined, + + animation_stack: DefinitionCounter.animation_stack + ? { + _key: 'ObjectType', + _values: ['AnimationStack'], + Count: DefinitionCounter.animation_stack, + PropertyTemplate: { + _values: ['FbxAnimStack'], + Properties70: { + P01: { + _key: 'P', + _values: ['Description', 'KString', '', '', ''], + }, + P02: { + _key: 'P', + _values: ['LocalStart', 'KTime', 'Time', '', TNum('L', 0)], + }, + P03: { + _key: 'P', + _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', 0)], + }, + P04: { + _key: 'P', + _values: [ + 'ReferenceStart', + 'KTime', + 'Time', + '', + TNum('L', 0), + ], + }, + P05: { + _key: 'P', + _values: [ + 'ReferenceStop', + 'KTime', + 'Time', + '', + TNum('L', 0), + ], + }, + }, + }, } - } - } : undefined, - animation_curve: DefinitionCounter.animation_curve ? { - _key: 'ObjectType', - _values: ['AnimationCurve'], - Count: DefinitionCounter.animation_curve, - } : undefined - } + : undefined, + animation_layer: DefinitionCounter.animation_layer + ? { + _key: 'ObjectType', + _values: ['AnimationLayer'], + Count: DefinitionCounter.animation_layer, + PropertyTemplate: { + _values: ['FbxAnimLayer'], + Properties70: { + P01: { + _key: 'P', + _values: ['Weight', 'Number', '', 'A', TNum('D', 100)], + }, + P02: { + _key: 'P', + _values: ['Mute', 'bool', '', '', TNum('I', 0)], + }, + P03: { + _key: 'P', + _values: ['Solo', 'bool', '', '', TNum('I', 0)], + }, + P04: { + _key: 'P', + _values: ['Lock', 'bool', '', '', TNum('I', 0)], + }, + P05: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P06: { _key: 'P', _values: ['BlendMode', 'enum', '', '', 0] }, + P07: { + _key: 'P', + _values: ['RotationAccumulationMode', 'enum', '', '', 0], + }, + P08: { + _key: 'P', + _values: ['ScaleAccumulationMode', 'enum', '', '', 0], + }, + P09: { + _key: 'P', + _values: ['BlendModeBypass', 'ULongLong', '', '', 0], + }, + }, + }, + } + : undefined, + animation_curve_node: DefinitionCounter.animation_curve_node + ? { + _key: 'ObjectType', + _values: ['AnimationCurveNode'], + Count: DefinitionCounter.animation_curve_node, + PropertyTemplate: { + _values: ['FbxAnimCurveNode'], + Properties70: { + P01: { _key: 'P', _values: ['d', 'Compound', '', ''] }, + }, + }, + } + : undefined, + animation_curve: DefinitionCounter.animation_curve + ? { + _key: 'ObjectType', + _values: ['AnimationCurve'], + Count: DefinitionCounter.animation_curve, + } + : undefined, + }, }) - model.push(formatFBXComment('Object properties')); + model.push(formatFBXComment('Object properties')) model.push({ - Objects - }); + Objects, + }) // Object connections - model.push(formatFBXComment('Object connections')); - let connections = {}; + model.push(formatFBXComment('Object connections')) + let connections = {} Connections.forEach((connection, i) => { - connections[`connection_${i}_comment`] = {_comment: connection.name.join(', ')} + connections[`connection_${i}_comment`] = { _comment: connection.name.join(', ') } connections[`connection_${i}`] = { _key: 'C', - _values: [connection.property ? 'OP' : 'OO', ...connection.id] + _values: [connection.property ? 'OP' : 'OO', ...connection.id], } if (connection.property) { - connections[`connection_${i}`]._values.push(connection.property); + connections[`connection_${i}`]._values.push(connection.property) } //model += `\t;${connection.name.join(', ')}\n`; //model += `\tC: "${property ? 'OP' : 'OO'}",${connection.id.join(',')}${property}\n\n`; }) model.push({ - Connections: connections - }); + Connections: connections, + }) // Takes (Animation) - model.push(formatFBXComment('Takes section')); + model.push(formatFBXComment('Takes section')) model.push({ - Takes + Takes, }) - scope.dispatchEvent('compile', {model, options}); + scope.dispatchEvent('compile', { model, options }) - let compiled_model; + let compiled_model if (options.encoding == 'binary') { - - let top_level_object = {}; + let top_level_object = {} model.forEach(section => { if (typeof section == 'object') { for (let key in section) { - top_level_object[key] = section[key]; + top_level_object[key] = section[key] } } }) - compiled_model = compileBinaryFBXModel(top_level_object); - + compiled_model = compileBinaryFBXModel(top_level_object) } else { - compiled_model = model.map(section => { - if (typeof section == 'object') { - return compileASCIIFBXSection(section); - } else { - return section; - } - }).join(''); + compiled_model = model + .map(section => { + if (typeof section == 'object') { + return compileASCIIFBXSection(section) + } else { + return section + } + }) + .join('') } - return compiled_model; + return compiled_model }, write(content, path) { - var scope = this; + var scope = this - Blockbench.writeFile(path, {content}, path => scope.afterSave(path)); + Blockbench.writeFile(path, { content }, path => scope.afterSave(path)) Texture.all.forEach(tex => { - if (tex.error) return; - var name = tex.name; + if (tex.error) return + var name = tex.name if (name.substr(-4).toLowerCase() !== '.png') { - name += '.png'; + name += '.png' } - var image_path = path.split(osfs); - image_path.splice(-1, 1, name); + var image_path = path.split(osfs) + image_path.splice(-1, 1, name) Blockbench.writeFile(image_path.join(osfs), { content: tex.source, - savetype: 'image' + savetype: 'image', }) }) }, export_options: { - encoding: {type: 'select', label: 'codec.common.encoding', options: {ascii: 'ASCII', binary: 'Binary (Experimental)'}}, - scale: {label: 'settings.model_export_scale', type: 'number', value: Settings.get('model_export_scale')}, - embed_textures: {type: 'checkbox', label: 'codec.common.embed_textures', value: false}, - include_animations: {label: 'codec.common.export_animations', type: 'checkbox', value: true} + encoding: { + type: 'select', + label: 'codec.common.encoding', + options: { ascii: 'ASCII', binary: 'Binary (Experimental)' }, + }, + scale: { + label: 'settings.model_export_scale', + type: 'number', + value: Settings.get('model_export_scale'), + }, + embed_textures: { type: 'checkbox', label: 'codec.common.embed_textures', value: false }, + include_animations: { + label: 'codec.common.export_animations', + type: 'checkbox', + value: true, + }, }, async export() { if (Object.keys(this.export_options).length) { - let result = await this.promptExportOptions(); - if (result === null) return; + let result = await this.promptExportOptions() + if (result === null) return } - var scope = this; + var scope = this if (isApp) { - Filesystem.exportFile({ - resource_id: 'fbx', - type: this.name, - extensions: [this.extension], - startpath: this.startPath(), - content: this.compile(), - name: this.fileName(), - custom_writer: (a, b) => scope.write(a, b), - }, path => this.afterDownload(path)) - + Filesystem.exportFile( + { + resource_id: 'fbx', + type: this.name, + extensions: [this.extension], + startpath: this.startPath(), + content: this.compile(), + name: this.fileName(), + custom_writer: (a, b) => scope.write(a, b), + }, + path => this.afterDownload(path) + ) } else { - var archive = new JSZip(); + var archive = new JSZip() var content = this.compile() - archive.file((Project.name||'model')+'.fbx', content) + archive.file((Project.name || 'model') + '.fbx', content) Texture.all.forEach(tex => { - if (tex.error) return; - var name = tex.name; + if (tex.error) return + var name = tex.name if (name.substr(-4).toLowerCase() !== '.png') { - name += '.png'; + name += '.png' } - archive.file(name, tex.source.replace('data:image/png;base64,', ''), {base64: true}); + archive.file(name, tex.source.replace('data:image/png;base64,', ''), { + base64: true, + }) }) - archive.generateAsync({type: 'blob'}).then(content => { - Filesystem.exportFile({ - type: 'Zip Archive', - extensions: ['zip'], - name: 'assets', - content: content, - savetype: 'zip' - }, path => scope.afterDownload(path)); + archive.generateAsync({ type: 'blob' }).then(content => { + Filesystem.exportFile( + { + type: 'Zip Archive', + extensions: ['zip'], + name: 'assets', + content: content, + savetype: 'zip', + }, + path => scope.afterDownload(path) + ) }) } - } + }, }) -BARS.defineActions(function() { +BARS.defineActions(function () { codec.export_action = new Action('export_fbx', { icon: 'icon-fbx', category: 'file', condition: () => Project, click: function () { codec.export() - } + }, }) }) class BinaryWriter { - array: Uint8Array; - buffer: ArrayBuffer; + array: Uint8Array + buffer: ArrayBuffer view: DataView cursor: number little_endian: boolean - textEncoder: TextEncoder; + textEncoder: TextEncoder constructor(minimal_length, little_endian) { - this.array = new Uint8Array(minimal_length); + this.array = new Uint8Array(minimal_length) // @ts-ignore - this.buffer = this.array.buffer; - this.view = new DataView(this.buffer); - this.cursor = 0; - this.little_endian = !!little_endian; - this.textEncoder = new TextEncoder(); + this.buffer = this.array.buffer + this.view = new DataView(this.buffer) + this.cursor = 0 + this.little_endian = !!little_endian + this.textEncoder = new TextEncoder() } expand(n) { - if (this.cursor+n > this.buffer.byteLength) { - var oldArray = this.array; + if (this.cursor + n > this.buffer.byteLength) { + var oldArray = this.array // Expand by at least 160 bytes at a time to improve performance. Only works for FBX since 176+ arbitrary bytes are added to the file end. - this.array = new Uint8Array(this.cursor + Math.max(n, 176)); - this.buffer = this.array.buffer; - this.array.set(oldArray); + this.array = new Uint8Array(this.cursor + Math.max(n, 176)) + this.buffer = this.array.buffer + this.array.set(oldArray) this.view = new DataView(this.buffer) } } WriteUInt8(value) { - this.expand(1); - this.view.setUint8(this.cursor, value); - this.cursor += 1; + this.expand(1) + this.view.setUint8(this.cursor, value) + this.cursor += 1 } WriteUInt16(value) { - this.expand(2); - this.view.setUint16(this.cursor, value, this.little_endian); - this.cursor += 2; + this.expand(2) + this.view.setUint16(this.cursor, value, this.little_endian) + this.cursor += 2 } WriteInt16(value) { - this.expand(2); - this.view.setInt16(this.cursor, value, this.little_endian); - this.cursor += 2; + this.expand(2) + this.view.setInt16(this.cursor, value, this.little_endian) + this.cursor += 2 } WriteInt32(value) { - this.expand(4); - this.view.setInt32(this.cursor, value, this.little_endian); - this.cursor += 4; + this.expand(4) + this.view.setInt32(this.cursor, value, this.little_endian) + this.cursor += 4 } WriteInt64(value) { - this.expand(8); - this.view.setBigInt64(this.cursor, BigInt(value), this.little_endian); - this.cursor += 8; + this.expand(8) + this.view.setBigInt64(this.cursor, BigInt(value), this.little_endian) + this.cursor += 8 } WriteUInt32(value) { - this.expand(4); - this.view.setUint32(this.cursor, value, this.little_endian); - this.cursor += 4; + this.expand(4) + this.view.setUint32(this.cursor, value, this.little_endian) + this.cursor += 4 } WriteFloat32(value) { - this.expand(4); - this.view.setFloat32(this.cursor, value, this.little_endian); - this.cursor += 4; + this.expand(4) + this.view.setFloat32(this.cursor, value, this.little_endian) + this.cursor += 4 } WriteFloat64(value) { - this.expand(8); - this.view.setFloat64(this.cursor, value, this.little_endian); - this.cursor += 8; + this.expand(8) + this.view.setFloat64(this.cursor, value, this.little_endian) + this.cursor += 8 } - WriteBoolean(value:boolean) { + WriteBoolean(value: boolean) { this.WriteUInt8(value ? 1 : 0) } Write7BitEncodedInt(value) { while (value >= 0x80) { - this.WriteUInt8(value | 0x80); - value = value >> 7; + this.WriteUInt8(value | 0x80) + value = value >> 7 } - this.WriteUInt8(value); + this.WriteUInt8(value) } WriteRawString(string: string) { - var array = this.EncodeString(string); - this.WriteBytes(array); + var array = this.EncodeString(string) + this.WriteBytes(array) } WriteString(string: string, raw?: boolean) { - var array = this.EncodeString(string); - if (!raw) this.Write7BitEncodedInt(array.byteLength); - this.WriteBytes(array); + var array = this.EncodeString(string) + if (!raw) this.Write7BitEncodedInt(array.byteLength) + this.WriteBytes(array) } WriteU32String(string: string) { - var array = this.EncodeString(string); - this.WriteUInt32(array.byteLength); - this.WriteBytes(array); + var array = this.EncodeString(string) + this.WriteUInt32(array.byteLength) + this.WriteBytes(array) } WriteU32Base64(base64) { - let data = patchedAtob(base64); - let array = Uint8Array.from(data, c => c.charCodeAt(0)); - this.WriteUInt32(array.length); - this.WriteBytes(array); + let data = patchedAtob(base64) + let array = Uint8Array.from(data, c => c.charCodeAt(0)) + this.WriteUInt32(array.length) + this.WriteBytes(array) } WritePoint(point) { - this.expand(8); - this.view.setInt32(this.cursor, point.x, this.little_endian); - this.cursor += 4; - this.view.setInt32(this.cursor, point.y, this.little_endian); - this.cursor += 4; + this.expand(8) + this.view.setInt32(this.cursor, point.x, this.little_endian) + this.cursor += 4 + this.view.setInt32(this.cursor, point.y, this.little_endian) + this.cursor += 4 } WriteVector2(vector) { - this.expand(8); - this.view.setFloat32(this.cursor, vector.x, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, vector.y, this.little_endian); - this.cursor += 4; + this.expand(8) + this.view.setFloat32(this.cursor, vector.x, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, vector.y, this.little_endian) + this.cursor += 4 } WriteVector3(vector) { - this.expand(12); - this.view.setFloat32(this.cursor, vector.x, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, vector.y, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, vector.z, this.little_endian); - this.cursor += 4; + this.expand(12) + this.view.setFloat32(this.cursor, vector.x, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, vector.y, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, vector.z, this.little_endian) + this.cursor += 4 } WriteIntVector3(vector) { - this.expand(12); - this.view.setInt32(this.cursor, vector.x, this.little_endian); - this.cursor += 4; - this.view.setInt32(this.cursor, vector.y, this.little_endian); - this.cursor += 4; - this.view.setInt32(this.cursor, vector.z, this.little_endian); - this.cursor += 4; + this.expand(12) + this.view.setInt32(this.cursor, vector.x, this.little_endian) + this.cursor += 4 + this.view.setInt32(this.cursor, vector.y, this.little_endian) + this.cursor += 4 + this.view.setInt32(this.cursor, vector.z, this.little_endian) + this.cursor += 4 } WriteQuaternion(quat) { - this.expand(16); - this.view.setFloat32(this.cursor, quat.w, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, quat.x, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, quat.y, this.little_endian); - this.cursor += 4; - this.view.setFloat32(this.cursor, quat.z, this.little_endian); - this.cursor += 4; + this.expand(16) + this.view.setFloat32(this.cursor, quat.w, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, quat.x, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, quat.y, this.little_endian) + this.cursor += 4 + this.view.setFloat32(this.cursor, quat.z, this.little_endian) + this.cursor += 4 } WriteBytes(array) { - this.expand(array.byteLength); - this.array.set(array, this.cursor); - this.cursor += array.byteLength; + this.expand(array.byteLength) + this.array.set(array, this.cursor) + this.cursor += array.byteLength } EncodeString(string: string) { - return this.textEncoder.encode(string); + return this.textEncoder.encode(string) } -}; +} export function compileBinaryFBXModel(top_level_object) { // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ // https://github.com/jskorepa/fbx.js/blob/master/src/lib/index.ts - let _BLOCK_SENTINEL_DATA; + let _BLOCK_SENTINEL_DATA if (_FBX_VERSION < 7500) { - _BLOCK_SENTINEL_DATA = new Uint8Array( - Array(13).fill(0x00) - ); - } - else { - _BLOCK_SENTINEL_DATA = new Uint8Array( - Array(25).fill(0x00) - ); + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(13).fill(0x00)) + } else { + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(25).fill(0x00)) } // Awful exceptions from Blender: those "classes" of elements seem to need block sentinel even when having no children and some props. - const _KEYS_IGNORE_BLOCK_SENTINEL = ["AnimationStack", "AnimationLayer"]; + const _KEYS_IGNORE_BLOCK_SENTINEL = ['AnimationStack', 'AnimationLayer'] // TODO: if FBX_VERSION >= 7500, use 64-bit offsets (for read_fbx_elem_uint) - var writer = new BinaryWriter(20, true); + var writer = new BinaryWriter(20, true) // Header - writer.WriteRawString('Kaydara FBX Binary '); - writer.WriteUInt8(0x00); - writer.WriteUInt8(0x1A); - writer.WriteUInt8(0x00); + writer.WriteRawString('Kaydara FBX Binary ') + writer.WriteUInt8(0x00) + writer.WriteUInt8(0x1a) + writer.WriteUInt8(0x00) // Version - writer.WriteUInt32(_FBX_VERSION); - + writer.WriteUInt32(_FBX_VERSION) function writeObjectRecursively(key, object) { - - let tuple; + let tuple if (typeof object == 'object' && typeof object.map === 'function') { - tuple = object; + tuple = object } else if (typeof object !== 'object') { - tuple = [object]; + tuple = [object] } else if (object._values) { - tuple = object._values; + tuple = object._values } else { - tuple = []; + tuple = [] } - let is_data_array = object.hasOwnProperty('_values') && object.hasOwnProperty('a') && - object._type != undefined; + let is_data_array = + object.hasOwnProperty('_values') && + object.hasOwnProperty('a') && + object._type != undefined // EndOffset, change later - let end_offset_index = writer.cursor; - writer.WriteUInt32(0); + let end_offset_index = writer.cursor + writer.WriteUInt32(0) // NumProperties - writer.WriteUInt32(tuple.length); + writer.WriteUInt32(tuple.length) // PropertyListLen, change later - let property_length_index = writer.cursor; - writer.WriteUInt32(0); + let property_length_index = writer.cursor + writer.WriteUInt32(0) // Name - writer.WriteString(key); + writer.WriteString(key) - - let property_start_index = writer.cursor; + let property_start_index = writer.cursor // Data Array if (is_data_array) { - let type = object._type || 'i'; + let type = object._type || 'i' if (!object._type) console.log('default', key, 'to int') - let array = object.a; - if (array instanceof Array == false) array = [array]; - - writer.WriteRawString(type); - writer.WriteUInt32(array.length); + let array = object.a + if (array instanceof Array == false) array = [array] + + writer.WriteRawString(type) + writer.WriteUInt32(array.length) // Encoding (compression, unused by Blockbench) - writer.WriteUInt32(0); + writer.WriteUInt32(0) // Compressed Length (but we don't use compression, so it's just the data length) - let data_size = 0; + let data_size = 0 switch (type) { case 'f': case 'i': - data_size = 4; - break; + data_size = 4 + break case 'd': case 'l': - data_size = 8; - break; + data_size = 8 + break case 'b': - data_size = 1; - break; + data_size = 1 + break } - writer.WriteUInt32(array.length * data_size); + writer.WriteUInt32(array.length * data_size) // Contents for (let v of array) { switch (type) { - case 'f': writer.WriteFloat32(v); break; - case 'd': writer.WriteFloat64(v); break; - case 'l': writer.WriteInt64(v); break; - case 'i': writer.WriteInt32(v); break; - case 'b': writer.WriteBoolean(v); break; + case 'f': + writer.WriteFloat32(v) + break + case 'd': + writer.WriteFloat64(v) + break + case 'l': + writer.WriteInt64(v) + break + case 'i': + writer.WriteInt32(v) + break + case 'b': + writer.WriteBoolean(v) + break } } } else { - // Tuple tuple.forEach((value, i) => { - let type: string = typeof value; + let type: string = typeof value if (typeof value == 'object' && value.isTNum) { - type = value.type; - value = value.value; + type = value.type + value = value.value } if (type == 'number') { - type = value % 1 ? 'D' : 'I'; + type = value % 1 ? 'D' : 'I' //if (!object._type) console.log('default', key, i, 'to', type, object) } // handle number types @@ -1720,189 +3015,186 @@ export function compileBinaryFBXModel(top_level_object) { // L: int64 if (type == 'boolean') { - writer.WriteRawString('C'); - writer.WriteBoolean(value); - + writer.WriteRawString('C') + writer.WriteBoolean(value) } else if (type == 'string' && value.startsWith('iV')) { // base64 - writer.WriteRawString('R'); - writer.WriteU32Base64(value); - + writer.WriteRawString('R') + writer.WriteU32Base64(value) } else if (type == 'string') { // Replace '::' with 0x00 0x01 and swap the order // E.g. "Geometry::cube" becomes "cube\x00\x01Geometry" // Will this break any normal text that contains those characters? I hope not. if (value.includes('::')) { - const hex00 = String.fromCharCode(0x00); - const hex01 = String.fromCharCode(0x01); - - const parts = value.split("::"); - value = parts[1] + hex00 + hex01 + parts[0]; + const hex00 = String.fromCharCode(0x00) + const hex01 = String.fromCharCode(0x01) + + const parts = value.split('::') + value = parts[1] + hex00 + hex01 + parts[0] } // string - writer.WriteRawString('S'); - if (value.startsWith('_')) value = value.substring(1); - writer.WriteU32String(value); - + writer.WriteRawString('S') + if (value.startsWith('_')) value = value.substring(1) + writer.WriteU32String(value) } else if (type == 'Y') { - writer.WriteRawString('Y'); - writer.WriteInt16(value); - + writer.WriteRawString('Y') + writer.WriteInt16(value) } else if (type == 'I') { - writer.WriteRawString('I'); - writer.WriteInt32(value); - + writer.WriteRawString('I') + writer.WriteInt32(value) } else if (type == 'F') { - writer.WriteRawString('F'); - writer.WriteFloat32(value); - + writer.WriteRawString('F') + writer.WriteFloat32(value) } else if (type == 'D') { - writer.WriteRawString('D'); - writer.WriteFloat64(value); - + writer.WriteRawString('D') + writer.WriteFloat64(value) } else if (type == 'L') { - writer.WriteRawString('L'); - writer.WriteInt64(value); - + writer.WriteRawString('L') + writer.WriteInt64(value) } - }) } // Property Byte Length - writer.view.setUint32(property_length_index, writer.cursor - property_start_index, writer.little_endian); + writer.view.setUint32( + property_length_index, + writer.cursor - property_start_index, + writer.little_endian + ) // Nested List if (typeof object == 'object' && object instanceof Array == false && !is_data_array) { - - let is_nested = false; + let is_nested = false for (let key in object) { - if (typeof key == 'string' && key.startsWith('_')) continue; - if (object[key] === undefined) continue; - let child = object[key]; - if (child === null || child._comment) continue; - if (child._key) key = child._key; + if (typeof key == 'string' && key.startsWith('_')) continue + if (object[key] === undefined) continue + let child = object[key] + if (child === null || child._comment) continue + if (child._key) key = child._key - is_nested = true; + is_nested = true - writeObjectRecursively(key, child); + writeObjectRecursively(key, child) } // Null Record, indicating a nested list. - if (is_nested || (Object.keys(object).length === 0 && !(_KEYS_IGNORE_BLOCK_SENTINEL.includes(key)))) { - writer.WriteBytes(_BLOCK_SENTINEL_DATA); + if ( + is_nested || + (Object.keys(object).length === 0 && !_KEYS_IGNORE_BLOCK_SENTINEL.includes(key)) + ) { + writer.WriteBytes(_BLOCK_SENTINEL_DATA) } } // End Offset - writer.view.setUint32(end_offset_index, writer.cursor, writer.little_endian); + writer.view.setUint32(end_offset_index, writer.cursor, writer.little_endian) } //writeObjectRecursively('', top_level_object); for (let key in top_level_object) { - writeObjectRecursively(key, top_level_object[key]); + writeObjectRecursively(key, top_level_object[key]) } - writer.WriteBytes(_BLOCK_SENTINEL_DATA); + writer.WriteBytes(_BLOCK_SENTINEL_DATA) // Footer // Write the FOOT ID let footer_id = [ - 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e, - 0x00, 0x00, 0x00, 0x00]; - writer.WriteBytes(new Uint8Array(footer_id)); + 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, + 0x7e, 0x00, 0x00, 0x00, 0x00, + ] + writer.WriteBytes(new Uint8Array(footer_id)) // padding for alignment (values between 1 & 16 observed) // if already aligned to 16, add a full 16 bytes padding. - const offset = writer.cursor; - let pad = ((offset + 15) & ~15) - offset; - if (pad === 0) pad = 16; + const offset = writer.cursor + let pad = ((offset + 15) & ~15) - offset + if (pad === 0) pad = 16 for (let i = 0; i < pad; i++) { - writer.WriteUInt8(0x00); + writer.WriteUInt8(0x00) } // Write the FBX version - writer.WriteUInt32(_FBX_VERSION); + writer.WriteUInt32(_FBX_VERSION) // Write some footer magic - writer.WriteBytes(new Uint8Array( - Array(120).fill(0x00)) - ); + writer.WriteBytes(new Uint8Array(Array(120).fill(0x00))) let footer_magic = [ - 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b - ]; - writer.WriteBytes(new Uint8Array(footer_magic)); + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, + 0x0b, + ] + writer.WriteBytes(new Uint8Array(footer_magic)) // Cut the array to the cursor location (because the writer expand method can have added extra bytes) - const output = writer.array.subarray(0, writer.cursor); + const output = writer.array.subarray(0, writer.cursor) - return output; + return output } export function compileASCIIFBXSection(object: FBXNode) { - let depth = 0; + let depth = 0 function indent() { - let spaces = ''; + let spaces = '' for (let i = 0; i < depth; i++) { - spaces += '\t'; + spaces += '\t' } - return spaces; + return spaces } function handleValue(value) { - if (typeof value == 'object' && value.isTNum) value = value.value; - if (typeof value == 'boolean') return value ? 'Y' : 'N'; - if (typeof value == 'string' && value.startsWith('_')) return value.substring(1); - if (typeof value == 'string') return '"' + value + '"'; - return value; + if (typeof value == 'object' && value.isTNum) value = value.value + if (typeof value == 'boolean') return value ? 'Y' : 'N' + if (typeof value == 'string' && value.startsWith('_')) return value.substring(1) + if (typeof value == 'string') return '"' + value + '"' + return value } function joinArray(array) { - let string = ''; - if (Array.length == 0) return string; - string += array[0]; + let string = '' + if (Array.length == 0) return string + string += array[0] for (let i = 1; i < array.length; i++) { - let item = array[i]; - string += ','; + let item = array[i] + string += ',' if (typeof item !== 'number') { - string += ' '; + string += ' ' } - string += item; + string += item } - return string; + return string } function handleObjectChildren(parent) { - let output = ''; + let output = '' for (let key in parent) { - if (typeof key == 'string' && key.startsWith('_')) continue; - if (parent[key] === undefined || parent[key] === null) continue; - let object = parent[key]; + if (typeof key == 'string' && key.startsWith('_')) continue + if (parent[key] === undefined || parent[key] === null) continue + let object = parent[key] if (object._comment) { - output += `\n${indent()};${object._comment}\n`; - continue; + output += `\n${indent()};${object._comment}\n` + continue } - if (object._key) key = object._key; + if (object._key) key = object._key - let values = ''; + let values = '' if (typeof object == 'object' && typeof object.map === 'function') { - values = joinArray(object.map(handleValue)); + values = joinArray(object.map(handleValue)) } else if (typeof object !== 'object') { - values = handleValue(object); + values = handleValue(object) } else if (object._values) { - values = joinArray(object._values.map(handleValue)); + values = joinArray(object._values.map(handleValue)) } - output += `${indent()}${key}: ${values}`; + output += `${indent()}${key}: ${values}` - let content; + let content if (typeof object == 'object' && typeof object.map !== 'function') { - depth++; - content = handleObjectChildren(object); - depth--; + depth++ + content = handleObjectChildren(object) + depth-- } if (content || object._force_compound) { - output += ` {\n${content}${indent()}}\n`; + output += ` {\n${content}${indent()}}\n` } else { - output += '\n'; + output += '\n' } } - return output; + return output } - return handleObjectChildren(object); + return handleObjectChildren(object) } diff --git a/js/io/formats/generic.ts b/js/io/formats/generic.ts index fe0e3dada..0067bc889 100644 --- a/js/io/formats/generic.ts +++ b/js/io/formats/generic.ts @@ -1,16 +1,24 @@ new ModelFormat('free', { icon: 'icon-format_free', category: 'general', - target: ['Godot', 'Unity', 'Unreal Engine', 'Sketchfab', 'Blender', tl('format.free.info.3d_printing')], + target: [ + 'Godot', + 'Unity', + 'Unreal Engine', + 'Sketchfab', + 'Blender', + tl('format.free.info.3d_printing'), + ], format_page: { content: [ - {type: 'h3', text: tl('mode.start.format.informations')}, - {text: `* ${tl('format.free.info.meshes')} - * ${tl('format.free.info.limitation')}`.replace(/\t+/g, '') + { type: 'h3', text: tl('mode.start.format.informations') }, + { + text: `* ${tl('format.free.info.meshes')} + * ${tl('format.free.info.limitation')}`.replace(/\t+/g, ''), }, - {type: 'h3', text: tl('mode.start.format.resources')}, - {text: `* [Low-Poly Modeling Tutorial](https://www.youtube.com/watch?v=WbyCbA1c8BM)`} - ] + { type: 'h3', text: tl('mode.start.format.resources') }, + { text: `* [Low-Poly Modeling Tutorial](https://www.youtube.com/watch?v=WbyCbA1c8BM)` }, + ], }, meshes: true, billboards: true, diff --git a/js/io/formats/skin.ts b/js/io/formats/skin.ts index f233a79d2..5cf967587 100644 --- a/js/io/formats/skin.ts +++ b/js/io/formats/skin.ts @@ -1,13 +1,13 @@ -import { CanvasFrame } from "../../lib/CanvasFrame" -import StateMemory from "../../util/state_memory"; -import { Panels, setProjectTitle } from "../../interface/interface" -import { getAllGroups } from "../../outliner/group" -import { DefaultCameraPresets } from "../../preview/preview" -import { MinecraftEULA } from "../../preview/preview_scenes" -import { TextureGenerator } from "../../texturing/texture_generator" -import { Panel } from "../../interface/panels"; -import { Blockbench } from "../../api"; -import { FormResultValue } from "../../interface/form"; +import { CanvasFrame } from '../../lib/CanvasFrame' +import StateMemory from '../../util/state_memory' +import { Panels, setProjectTitle } from '../../interface/interface' +import { getAllGroups } from '../../outliner/group' +import { DefaultCameraPresets } from '../../preview/preview' +import { MinecraftEULA } from '../../preview/preview_scenes' +import { TextureGenerator } from '../../texturing/texture_generator' +import { Panel } from '../../interface/panels' +import { Blockbench } from '../../api' +import { FormResultValue } from '../../interface/form' type SkinPreset = { display_name: string @@ -22,9 +22,9 @@ type SkinPreset = { } } } -export const skin_presets: Record = {}; +export const skin_presets: Record = {} -type SkinPoseData = Record +type SkinPoseData = Record const DefaultPoses: Record = { none: { Head: [0, 0, 0], @@ -48,15 +48,15 @@ const DefaultPoses: Record = { RightArm: [-35, 0, 0], LeftArm: [35, 0, 0], RightLeg: [42, 0, 2], - LeftLeg: [-42, 0, -2] + LeftLeg: [-42, 0, -2], }, crouching: { - Head: {rotation: [-5, 0, 0], offset: [0, -1, 0]}, - Body: {rotation: [-28, 0, 0], offset: [0, 0, -1]}, + Head: { rotation: [-5, 0, 0], offset: [0, -1, 0] }, + Body: { rotation: [-28, 0, 0], offset: [0, 0, -1] }, RightArm: [-15, 0, 0], LeftArm: [-40, 0, 0], - RightLeg: {rotation: [-14, 0, 0], offset: [0, 3, 3.75]}, - LeftLeg: {rotation: [14, 0, 0], offset: [0, 3, 4]} + RightLeg: { rotation: [-14, 0, 0], offset: [0, 3, 3.75] }, + LeftLeg: { rotation: [14, 0, 0], offset: [0, 3, 4] }, }, sitting: { Head: [5.5, 0, 0], @@ -64,31 +64,31 @@ const DefaultPoses: Record = { RightArm: [36, 0, 0], LeftArm: [36, 0, 0], RightLeg: [72, -18, 0], - LeftLeg: [72, 18, 0] + LeftLeg: [72, 18, 0], }, jumping: { Head: [20, 0, 0], Body: [0, 0, 0], - RightArm: {rotation: [-175, 0, -20], offset: [0, 2, 0]}, - LeftArm: {rotation: [-170, 0, 15], offset: [0, 2, 0]}, - RightLeg: {rotation: [-5, 0, 15], offset: [0, -1, 0]}, - LeftLeg: {rotation: [2.5, 0, -10], offset: [0, 6, -3.75]} + RightArm: { rotation: [-175, 0, -20], offset: [0, 2, 0] }, + LeftArm: { rotation: [-170, 0, 15], offset: [0, 2, 0] }, + RightLeg: { rotation: [-5, 0, 15], offset: [0, -1, 0] }, + LeftLeg: { rotation: [2.5, 0, -10], offset: [0, 6, -3.75] }, }, aiming: { Head: [8, -35, 0], Body: [-2, 0, 0], - RightArm: {rotation: [97, -17, -2], offset: [-1, 1, -1]}, + RightArm: { rotation: [97, -17, -2], offset: [-1, 1, -1] }, LeftArm: [104, -44, -10], - RightLeg: {rotation: [2.5, 0, 0], offset: [0, 1, -2]}, - LeftLeg: [-28, 0, 0] + RightLeg: { rotation: [2.5, 0, 0], offset: [0, 1, -2] }, + LeftLeg: [-28, 0, 0], }, -}; +} export const codec = new Codec('skin_model', { name: 'Skin Model', remember: false, compile(options) { - if (options === undefined) options = 0; + if (options === undefined) options = 0 type BoneData = { name: string parent?: string @@ -107,17 +107,17 @@ export const codec = new Codec('skin_model', { name: Project.geometry_name.split('.')[0], texturewidth: Project.texture_width, textureheight: Project.texture_height, - bones: undefined as undefined | BoneData[] + bones: undefined as undefined | BoneData[], } let bones = [] - let groups = getAllGroups(); + let groups = getAllGroups() - groups.forEach(function(g) { - if (g.type !== 'group') return; + groups.forEach(function (g) { + if (g.type !== 'group') return //Bone let bone: BoneData = { - name: g.name + name: g.name, } bone.name = g.name if (g.parent.type === 'group') { @@ -126,21 +126,17 @@ export const codec = new Codec('skin_model', { bone.pivot = g.origin.slice() bone.pivot[0] *= -1 if (!g.rotation.allEqual(0)) { - bone.rotation = [ - -g.rotation[0], - -g.rotation[1], - g.rotation[2] - ] + bone.rotation = [-g.rotation[0], -g.rotation[1], g.rotation[2]] } - if (g.reset) bone.reset = true; - if (g.mirror_uv) bone.mirror = true; + if (g.reset) bone.reset = true + if (g.mirror_uv) bone.mirror = true //Elements let cubes = [] for (let obj of g.children) { if (obj.export && obj instanceof Cube) { // @ts-ignore - let template = Codecs.bedrock.compileCube(obj, g); + let template = Codecs.bedrock.compileCube(obj, g) cubes.push(template) } } @@ -153,65 +149,71 @@ export const codec = new Codec('skin_model', { if (bones.length) { entitymodel.bones = bones } - this.dispatchEvent('compile', {model: entitymodel, options}); + this.dispatchEvent('compile', { model: entitymodel, options }) return entitymodel }, // @ts-ignore - parse(data: any, resolution: number, texture_file: undefined | false | {name: string, path: string, content: string}, pose = true, layer_template) { - this.dispatchEvent('parse', {model: data}); - Project.texture_width = data.texturewidth || 64; - Project.texture_height = data.textureheight || 64; - if (data.texture_resolution_factor) resolution *= data.texture_resolution_factor; + parse( + data: any, + resolution: number, + texture_file: undefined | false | { name: string; path: string; content: string }, + pose = true, + layer_template + ) { + this.dispatchEvent('parse', { model: data }) + Project.texture_width = data.texturewidth || 64 + Project.texture_height = data.textureheight || 64 + if (data.texture_resolution_factor) resolution *= data.texture_resolution_factor - Interface.Panels.skin_pose.inside_vue.pose = Project.skin_pose = pose ? 'natural' : 'none'; + Interface.Panels.skin_pose.inside_vue.pose = Project.skin_pose = pose ? 'natural' : 'none' let bones = {} - let template_cubes = {}; + let template_cubes = {} if (data.bones) { let included_bones = [] - data.bones.forEach(function(b) { + data.bones.forEach(function (b) { included_bones.push(b.name) }) - data.bones.forEach(function(b, bi) { + data.bones.forEach(function (b, bi) { let group = new Group({ name: b.name, origin: b.pivot, - rotation: (pose && b.pose) ? b.pose : b.rotation + rotation: pose && b.pose ? b.pose : b.rotation, }).init() - group.isOpen = true; + group.isOpen = true bones[b.name] = group if (b.pivot) { group.origin[0] *= -1 } - group.rotation[0] *= -1; - group.rotation[1] *= -1; - + group.rotation[0] *= -1 + group.rotation[1] *= -1 + group.mirror_uv = b.mirror === true group.reset = b.reset === true - group.skin_original_origin = group.origin.slice() as ArrayVector3; + group.skin_original_origin = group.origin.slice() as ArrayVector3 if (b.cubes) { - b.cubes.forEach(function(cube) { - - let base_cube = Codecs.bedrock.parseCube(cube, group); - template_cubes[Cube.all.indexOf(base_cube)] = cube; - + b.cubes.forEach(function (cube) { + let base_cube = Codecs.bedrock.parseCube(cube, group) + template_cubes[Cube.all.indexOf(base_cube)] = cube }) } if (b.children) { - b.children.forEach(function(cg) { + b.children.forEach(function (cg) { cg.addTo(group) }) } - let parent_group: 'root' | OutlinerNode = 'root'; + let parent_group: 'root' | OutlinerNode = 'root' if (b.parent) { if (bones[b.parent]) { parent_group = bones[b.parent] } else { - data.bones.forEach(function(ib) { + data.bones.forEach(function (ib) { if (ib.name === b.parent) { - ib.children && ib.children.length ? ib.children.push(group) : ib.children = [group] + ib.children && ib.children.length + ? ib.children.push(group) + : (ib.children = [group]) } }) } @@ -220,15 +222,15 @@ export const codec = new Codec('skin_model', { }) } if (!Cube.all.find(cube => cube.box_uv)) { - Project.box_uv = false; + Project.box_uv = false } - let texture: Texture | undefined; + let texture: Texture | undefined if (typeof texture_file == 'object') { - texture = new Texture().fromFile(texture_file).add(false); + texture = new Texture().fromFile(texture_file).add(false) } else if (texture_file != false && resolution) { texture = generateTemplate( - Project.texture_width*resolution, - Project.texture_height*resolution, + Project.texture_width * resolution, + Project.texture_height * resolution, template_cubes, data.name, data.eyes, @@ -237,12 +239,12 @@ export const codec = new Codec('skin_model', { } for (let index in template_cubes) { if (template_cubes[index].visibility === false) { - Cube.all[index].visibility = false; + Cube.all[index].visibility = false } } if (texture) { - texture.load_callback = function() { - Modes.options.paint.select(); + texture.load_callback = function () { + Modes.options.paint.select() } } if (data.camera_angle) { @@ -255,35 +257,38 @@ export const codec = new Codec('skin_model', { updateSelection() }, }) -codec.export = null; -codec.rebuild = function(model_id: string, pose?: string) { - let [preset_id, variant] = model_id.split('.'); - let preset = skin_presets[preset_id]; - let model_raw = preset.model || (variant == 'java' ? preset.model_java : preset.model_bedrock) || preset.variants[variant].model; - let model = JSON.parse(model_raw); +codec.export = null +codec.rebuild = function (model_id: string, pose?: string) { + let [preset_id, variant] = model_id.split('.') + let preset = skin_presets[preset_id] + let model_raw = + preset.model || + (variant == 'java' ? preset.model_java : preset.model_bedrock) || + preset.variants[variant].model + let model = JSON.parse(model_raw) // @ts-ignore - codec.parse(model, undefined, true, pose && pose !== 'none'); + codec.parse(model, undefined, true, pose && pose !== 'none') if (pose && pose !== 'none' && pose !== 'natural') { setTimeout(() => { - setDefaultPose(pose); + setDefaultPose(pose) }, 1) } } - export const format = new ModelFormat('skin', { icon: 'icon-player', category: 'minecraft', target: ['Minecraft: Java Edition', 'Minecraft: Bedrock Edition'], format_page: { content: [ - {type: 'h3', text: tl('mode.start.format.informations')}, - {text: `* ${tl('format.skin.info.skin')} - * ${tl('format.skin.info.model')}`.replace(/\t+/g, '') + { type: 'h3', text: tl('mode.start.format.informations') }, + { + text: `* ${tl('format.skin.info.skin')} + * ${tl('format.skin.info.model')}`.replace(/\t+/g, ''), }, - {type: 'h3', text: tl('mode.start.format.resources')}, - {text: `* [Skin Design Tutorial](https://youtu.be/xC81Q3HGraE)`} - ] + { type: 'h3', text: tl('mode.start.format.resources') }, + { text: `* [Skin Design Tutorial](https://youtu.be/xC81Q3HGraE)` }, + ], }, can_convert_to: false, model_identifier: false, @@ -295,105 +300,117 @@ export const format = new ModelFormat('skin', { rotate_cubes: false, edit_mode: false, pose_mode: true, - codec + codec, }) -format.new = function() { - skin_dialog.show(); - return true; +format.new = function () { + skin_dialog.show() + return true } -skin_presets; - +skin_presets function setDefaultPose(pose_id: string) { - let angles = DefaultPoses[pose_id]; - loadPose(angles); - Panels.skin_pose.inside_vue.pose = pose_id; - Project.skin_pose = pose_id; + let angles = DefaultPoses[pose_id] + loadPose(angles) + Panels.skin_pose.inside_vue.pose = pose_id + Project.skin_pose = pose_id } function loadPose(pose_data: SkinPoseData) { - Panels.skin_pose.inside_vue.pose = ''; + Panels.skin_pose.inside_vue.pose = '' Group.all.forEach(group => { - if (!group.skin_original_origin) return; - let offset = group.origin.slice().V3_subtract(group.skin_original_origin); - group.origin.V3_set(group.skin_original_origin); + if (!group.skin_original_origin) return + let offset = group.origin.slice().V3_subtract(group.skin_original_origin) + group.origin.V3_set(group.skin_original_origin) }) for (let name in pose_data) { - let group = Group.all.find(g => g.name == name || g.name.replace(/\s/g, '') == name); + let group = Group.all.find(g => g.name == name || g.name.replace(/\s/g, '') == name) if (group) { if (pose_data[name] instanceof Array) { - group.extend({rotation: pose_data[name]}); + group.extend({ rotation: pose_data[name] }) } else { - group.extend({rotation: pose_data[name].rotation}); - group.origin.V3_add(pose_data[name].offset); + group.extend({ rotation: pose_data[name].rotation }) + group.origin.V3_add(pose_data[name].offset) } } } Canvas.updateView({ groups: Group.all, - group_aspects: {transform: true} + group_aspects: { transform: true }, }) } function getPoseData(): SkinPoseData { - const data: SkinPoseData = {}; + const data: SkinPoseData = {} for (let group of Group.all) { - if (!group.skin_original_origin) continue; - let offset = group.origin.slice().V3_subtract(group.skin_original_origin); - let rotation = group.rotation.slice() as ArrayVector3; + if (!group.skin_original_origin) continue + let offset = group.origin.slice().V3_subtract(group.skin_original_origin) + let rotation = group.rotation.slice() as ArrayVector3 if (offset.allEqual(0) == false) { data[group.name] = { - offset, rotation + offset, + rotation, } } else if (rotation.allEqual(0) == false) { - data[group.name] = rotation; + data[group.name] = rotation } } - return data; + return data } -export function generateTemplate(width = 64, height = 64, cubes, name = 'name', eyes, layer_template): Texture { - +export function generateTemplate( + width = 64, + height = 64, + cubes, + name = 'name', + eyes, + layer_template +): Texture { let texture = new Texture({ internal: true, - name: name+'.png' + name: name + '.png', }) let canvas = document.createElement('canvas') - let ctx = canvas.getContext('2d'); - canvas.width = width; - canvas.height = height; + let ctx = canvas.getContext('2d') + canvas.width = width + canvas.height = height if (Project.box_uv) { Cube.all.forEach((cube, i) => { - let template_cube = cubes[i]; + let template_cube = cubes[i] if (layer_template || !template_cube.layer) { - TextureGenerator.paintCubeBoxTemplate(cube, texture, canvas, null, template_cube.layer); + TextureGenerator.paintCubeBoxTemplate( + cube, + texture, + canvas, + null, + template_cube.layer + ) } }) } else if (cubes[0] && !cubes[0].layer) { - ctx.fillStyle = TextureGenerator.face_data.up.c1; + ctx.fillStyle = TextureGenerator.face_data.up.c1 ctx.fillRect(0, 0, width, height) - ctx.fillStyle = TextureGenerator.face_data.up.c2; - ctx.fillRect(1, 1, width-2, height-2) + ctx.fillStyle = TextureGenerator.face_data.up.c2 + ctx.fillRect(1, 1, width - 2, height - 2) } if (eyes) { - let res_multiple = canvas.width/Project.texture_width; - ctx.fillStyle = '#cdefff'; + let res_multiple = canvas.width / Project.texture_width + ctx.fillStyle = '#cdefff' eyes.forEach(eye => { ctx.fillRect( - eye[0]*res_multiple, - eye[1]*res_multiple, - (eye[2]||2)*res_multiple, - (eye[3]||2)*res_multiple + eye[0] * res_multiple, + eye[1] * res_multiple, + (eye[2] || 2) * res_multiple, + (eye[3] || 2) * res_multiple ) }) } - let dataUrl = canvas.toDataURL(); - texture.fromDataURL(dataUrl).add(false); - return texture; + let dataUrl = canvas.toDataURL() + texture.fromDataURL(dataUrl).add(false) + return texture } -export const model_options: Record = {}; -let selected_model = ''; +export const model_options: Record = {} +let selected_model = '' export const skin_dialog = new Dialog({ title: tl('dialog.skin.title'), id: 'skin', @@ -401,7 +418,7 @@ export const skin_dialog = new Dialog({ model: { label: 'dialog.skin.model', type: 'select', - options: model_options + options: model_options, }, game_edition: { label: 'dialog.skin.variant', @@ -412,8 +429,8 @@ export const skin_dialog = new Dialog({ bedrock_edition: 'Bedrock Edition', }, condition(form: Record) { - return skin_presets[form.model as string].model_bedrock; - } + return skin_presets[form.model as string].model_bedrock + }, }, variant: { label: 'dialog.skin.variant', @@ -422,28 +439,37 @@ export const skin_dialog = new Dialog({ return (selected_model && skin_presets[selected_model].variants) || {} }, condition(form) { - return skin_presets[form.model].variants; - } + return skin_presets[form.model].variants + }, + }, + resolution: { + label: 'dialog.create_texture.resolution', + type: 'select', + value: 1, + options: { + 1: 'generic.default', + 16: '16x', + 32: '32x', + 64: '64x', + 128: '128x', + }, }, - resolution: {label: 'dialog.create_texture.resolution', type: 'select', value: 1, options: { - 1: 'generic.default', - 16: '16x', - 32: '32x', - 64: '64x', - 128: '128x', - }}, resolution_warning: { - type: 'info', text: 'dialog.skin.high_res_texture', - condition: (form) => form.resolution > 16 && (form.model == 'steve' || form.model == 'alex') + type: 'info', + text: 'dialog.skin.high_res_texture', + condition: form => + form.resolution > 16 && (form.model == 'steve' || form.model == 'alex'), }, texture_source: { label: 'dialog.skin.texture_source', type: 'select', options: { template: 'dialog.skin.texture_source.template', - load_texture: navigator.onLine ? 'dialog.skin.texture_source.load_texture' : undefined, + load_texture: navigator.onLine + ? 'dialog.skin.texture_source.load_texture' + : undefined, upload_texture: 'dialog.skin.texture_file', - } + }, }, texture_file: { label: 'dialog.skin.texture_file', @@ -452,104 +478,118 @@ export const skin_dialog = new Dialog({ extensions: ['png'], readtype: 'image', filetype: 'PNG', - return_as: 'file' + return_as: 'file', + }, + pose: { + type: 'checkbox', + label: 'dialog.skin.pose', + value: true, + condition: form => !!skin_presets[form.model].pose, }, - pose: {type: 'checkbox', label: 'dialog.skin.pose', value: true, condition: form => (!!skin_presets[form.model].pose)}, - layer_template: {type: 'checkbox', label: 'dialog.skin.layer_template', value: false} + layer_template: { type: 'checkbox', label: 'dialog.skin.layer_template', value: false }, }, onFormChange(result) { - selected_model = result.model as string; - let variants = skin_presets[result.model as string].variants; + selected_model = result.model as string + let variants = skin_presets[result.model as string].variants if (variants) { for (let key in variants) { if (!result.variant || !variants[result.variant as string]) { - result.variant = key; - skin_dialog.setFormValues({variant: key}, false); - break; + result.variant = key + skin_dialog.setFormValues({ variant: key }, false) + break } } } }, - onConfirm: function(result) { + onConfirm: function (result) { if (result.model == 'flat_texture') { if (result.texture) { - Codecs.image.load(result.texture); + Codecs.image.load(result.texture) } else { - Formats.image.new(); + Formats.image.new() } - } else { if (newProject(format)) { - let preset = skin_presets[result.model]; - let raw_model: string; + let preset = skin_presets[result.model] + let raw_model: string if (preset.model_bedrock) { - raw_model = result.game_edition == 'java_edition' ? preset.model_java : preset.model_bedrock; + raw_model = + result.game_edition == 'java_edition' + ? preset.model_java + : preset.model_bedrock } else if (preset.variants) { - raw_model = preset.variants[result.variant].model; + raw_model = preset.variants[result.variant].model } else { - raw_model = preset.model; + raw_model = preset.model } - let model = JSON.parse(raw_model); - let resolution = result.resolution; + let model = JSON.parse(raw_model) + let resolution = result.resolution if (resolution == 1) { - resolution = model.default_resolution ?? 16; + resolution = model.default_resolution ?? 16 } - let texture: string | undefined | false; + let texture: string | undefined | false if (result.texture_source == 'upload_texture') { - texture = result.texture_file; + texture = result.texture_file } else if (result.texture_source == 'load_texture') { if (!model.external_textures) { - Blockbench.showQuickMessage('This skin model does not support loading textures from Minecraft at the moment', 3000); + Blockbench.showQuickMessage( + 'This skin model does not support loading textures from Minecraft at the moment', + 3000 + ) } else if (!navigator.onLine) { - Blockbench.showQuickMessage('Failed to load skin texture from Minecraft. Check your internet connection.', 3000); + Blockbench.showQuickMessage( + 'Failed to load skin texture from Minecraft. Check your internet connection.', + 3000 + ) } else { - texture = false; + texture = false } } // @ts-ignore - codec.parse(model, resolution / 16, texture, result.pose, result.layer_template); - Project.skin_model = result.model; + codec.parse(model, resolution / 16, texture, result.pose, result.layer_template) + Project.skin_model = result.model if (preset.model_bedrock) { - Project.skin_model += '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock'); + Project.skin_model += + '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock') } else if (preset.variants) { - Project.skin_model += '.' + result.variant; + Project.skin_model += '.' + result.variant } if (result.texture_source == 'load_texture' && navigator.onLine) { - MinecraftEULA.promptUser('skin').then(async function(accepted) { - if (accepted != true) return; - if (!model.external_textures) return; + MinecraftEULA.promptUser('skin').then(async function (accepted) { + if (accepted != true) return + if (!model.external_textures) return for (let path of model.external_textures) { - let frame = new CanvasFrame(); - let resource_path = `https://github.com/Mojang/bedrock-samples/blob/main/resource_pack/textures/${path}?raw=true`; + let frame = new CanvasFrame() + let resource_path = `https://github.com/Mojang/bedrock-samples/blob/main/resource_pack/textures/${path}?raw=true` frame.loadFromURL(resource_path).then(() => { - let dataUrl = (frame.canvas as HTMLCanvasElement).toDataURL(); + let dataUrl = (frame.canvas as HTMLCanvasElement).toDataURL() let texture = new Texture({ internal: true, - name: pathToName(path, true) - }); - texture.fromDataURL(dataUrl).add(false); - }); + name: pathToName(path, true), + }) + texture.fromDataURL(dataUrl).add(false) + }) } - }); + }) } } } }, onCancel() { - Blockbench.Format = 0; - Settings.updateSettingsInProfiles(); - } -}); + Blockbench.Format = 0 + Settings.updateSettingsInProfiles() + }, +}) // @ts-ignore -format.setup_dialog = skin_dialog; +format.setup_dialog = skin_dialog type SkinPose = { name: string data: SkinPoseData } -StateMemory.init('skin_poses', 'array'); +StateMemory.init('skin_poses', 'array') -BARS.defineActions(function() { +BARS.defineActions(function () { new Mode('pose', { icon: 'emoji_people', default_tool: 'rotate_tool', @@ -560,30 +600,30 @@ BARS.defineActions(function() { new Action('toggle_skin_layer', { icon: 'layers_clear', category: 'edit', - condition: {formats: ['skin']}, + condition: { formats: ['skin'] }, click: function () { - let edited = []; + let edited = [] Cube.all.forEach(cube => { if (cube.name.toLowerCase().includes('layer')) { - edited.push(cube); + edited.push(cube) } }) - if (!edited.length) return; - Undo.initEdit({elements: edited}); - let value = !edited[0].visibility; + if (!edited.length) return + Undo.initEdit({ elements: edited }) + let value = !edited[0].visibility edited.forEach(cube => { - cube.visibility = value; + cube.visibility = value }) - Undo.finishEdit('Toggle skin layer'); + Undo.finishEdit('Toggle skin layer') Canvas.updateVisibility() - } + }, }) new Action('convert_minecraft_skin_variant', { icon: 'compare_arrows', category: 'edit', - condition: {formats: ['skin'], method: () => Group.all.find(g => g.name == 'Right Arm')}, + condition: { formats: ['skin'], method: () => Group.all.find(g => g.name == 'Right Arm') }, click() { - let is_slim = Cube.all.find(c => c.name.match(/arm/i)).size(0) == 3; + let is_slim = Cube.all.find(c => c.name.match(/arm/i)).size(0) == 3 new Dialog('convert_minecraft_skin_variant', { title: 'action.convert_minecraft_skin_variant', form: { @@ -594,214 +634,242 @@ BARS.defineActions(function() { options: { steve: skin_presets.steve.display_name, alex: skin_presets.alex.display_name, - } + }, }, adjust_texture: { label: 'dialog.convert_skin.adjust_texture', type: 'checkbox', value: true, - } + }, }, onConfirm(result) { - let right_arm = Group.all.find(g => g.name == 'Right Arm')?.children?.filter(el => el instanceof Cube) ?? []; - let left_arm = Group.all.find(g => g.name == 'Left Arm')?.children?.filter(el => el instanceof Cube) ?? []; - let elements = right_arm.concat(left_arm); - Undo.initEdit({elements}); + let right_arm = + Group.all + .find(g => g.name == 'Right Arm') + ?.children?.filter(el => el instanceof Cube) ?? [] + let left_arm = + Group.all + .find(g => g.name == 'Left Arm') + ?.children?.filter(el => el instanceof Cube) ?? [] + let elements = right_arm.concat(left_arm) + Undo.initEdit({ elements }) for (let cube of right_arm) { - cube.to[0] = result.model == 'alex' ? 7 : 8; + cube.to[0] = result.model == 'alex' ? 7 : 8 } for (let cube of left_arm) { - cube.from[0] = result.model == 'alex' ? -7 : -8; + cube.from[0] = result.model == 'alex' ? -7 : -8 } - Canvas.updateView({elements: right_arm.concat(left_arm), element_aspects: {geometry: true, uv: true}, selection: true}); - Undo.finishEdit('Convert Minecraft skin model'); + Canvas.updateView({ + elements: right_arm.concat(left_arm), + element_aspects: { geometry: true, uv: true }, + selection: true, + }) + Undo.finishEdit('Convert Minecraft skin model') if (result.adjust_texture) { - let textures = Texture.all.filter(tex => tex.selected || tex.multi_selected); + let textures = Texture.all.filter(tex => tex.selected || tex.multi_selected) if (!textures.length) textures = [Texture.getDefault()] - if (!textures[0]) return; - + if (!textures[0]) return + const arm_uv_positions: [number, number][] = [ [40, 16], [40, 32], [32, 48], [48, 48], - ]; - type Translation = {area: [number, number, number, number], offset: [number, number]}; - let translations: Translation[]; + ] + type Translation = { + area: [number, number, number, number] + offset: [number, number] + } + let translations: Translation[] if (result.model == 'alex') { translations = [ - {area: [6, 0, 10, 16], offset: [-1, 0]}, - {area: [9, 0, 2, 4], offset: [-1, 0]}, - {area: [13, 4, 2, 12], offset: [-1, 0]}, - ]; + { area: [6, 0, 10, 16], offset: [-1, 0] }, + { area: [9, 0, 2, 4], offset: [-1, 0] }, + { area: [13, 4, 2, 12], offset: [-1, 0] }, + ] } else { translations = [ - {area: [5, 0, 10, 16], offset: [1, 0]}, - {area: [9, 0, 2, 4], offset: [1, 0]}, - {area: [13, 4, 2, 12], offset: [1, 0]}, - ]; + { area: [5, 0, 10, 16], offset: [1, 0] }, + { area: [9, 0, 2, 4], offset: [1, 0] }, + { area: [13, 4, 2, 12], offset: [1, 0] }, + ] } - Undo.initEdit({textures, bitmap: true}); + Undo.initEdit({ textures, bitmap: true }) for (let texture of textures) { if (texture.layers_enabled) { - texture.layers_enabled = false; - texture.selected_layer = null; - texture.layers.empty(); + texture.layers_enabled = false + texture.selected_layer = null + texture.layers.empty() } - texture.edit(() => { - let ctx = texture.ctx; - for (let position of arm_uv_positions) { - for (let translation of translations) { - let data = ctx.getImageData( - position[0] + translation.area[0], - position[1] + translation.area[1], - translation.area[2], - translation.area[3], - ); - ctx.putImageData(data, - position[0] + translation.area[0] + translation.offset[0], - position[1] + translation.area[1] + translation.offset[1] - ); + texture.edit( + () => { + let ctx = texture.ctx + for (let position of arm_uv_positions) { + for (let translation of translations) { + let data = ctx.getImageData( + position[0] + translation.area[0], + position[1] + translation.area[1], + translation.area[2], + translation.area[3] + ) + ctx.putImageData( + data, + position[0] + + translation.area[0] + + translation.offset[0], + position[1] + + translation.area[1] + + translation.offset[1] + ) + } + if (result.model == 'alex') { + ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4) + ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12) + } } - if (result.model == 'alex') { - ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4); - ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12); - } - } - }, {no_undo: true}); + }, + { no_undo: true } + ) } - UVEditor.vue.layer = null; - updateSelection(); - Undo.finishEdit('Convert Minecraft skin texture'); + UVEditor.vue.layer = null + updateSelection() + Undo.finishEdit('Convert Minecraft skin texture') } - } - }).show(); - } + }, + }).show() + }, }) new Action('export_minecraft_skin', { icon: 'icon-player', category: 'file', condition: () => Format == format && Texture.all[0], click: function () { - Texture.all[0].save(true); - } + Texture.all[0].save(true) + }, }) - + let explode_skin_model = new Toggle('explode_skin_model', { icon: () => 'open_in_full', category: 'edit', - condition: {formats: ['skin']}, + condition: { formats: ['skin'] }, default: false, onChange(exploded_view) { - Undo.initEdit({elements: Cube.all, exploded_view: !exploded_view}); + Undo.initEdit({ elements: Cube.all, exploded_view: !exploded_view }) Cube.all.forEach(cube => { let center = [ cube.from[0] + (cube.to[0] - cube.from[0]) / 2, cube.from[1], cube.from[2] + (cube.to[2] - cube.from[2]) / 2, ] - let offset = cube.name.toLowerCase().includes('leg') ? 1 : 0.5; - center.V3_multiply(exploded_view ? offset : -offset/(1+offset)); - cube.from.V3_add(center as ArrayVector3); - cube.to.V3_add(center as ArrayVector3); + let offset = cube.name.toLowerCase().includes('leg') ? 1 : 0.5 + center.V3_multiply(exploded_view ? offset : -offset / (1 + offset)) + cube.from.V3_add(center as ArrayVector3) + cube.to.V3_add(center as ArrayVector3) }) - Project.exploded_view = exploded_view; - Undo.finishEdit(exploded_view ? 'Explode skin model' : 'Revert exploding skin model', {elements: Cube.all, exploded_view: exploded_view}); - Canvas.updateView({elements: Cube.all, element_aspects: {geometry: true}}); - this.setIcon(this.icon); - } + Project.exploded_view = exploded_view + Undo.finishEdit(exploded_view ? 'Explode skin model' : 'Revert exploding skin model', { + elements: Cube.all, + exploded_view: exploded_view, + }) + Canvas.updateView({ elements: Cube.all, element_aspects: { geometry: true } }) + this.setIcon(this.icon) + }, }) Blockbench.on('select_project', () => { - explode_skin_model.value = !!Project.exploded_view; - explode_skin_model.updateEnabledState(); + explode_skin_model.value = !!Project.exploded_view + explode_skin_model.updateEnabledState() }) - new Action('custom_skin_poses', { icon: 'format_list_bulleted', category: 'view', - condition: {formats: ['skin'], modes: ['pose']}, + condition: { formats: ['skin'], modes: ['pose'] }, click(e) { - new Menu(this.children()).open(e.target as HTMLElement); + new Menu(this.children()).open(e.target as HTMLElement) }, children() { - let options = []; - let memory_list = StateMemory.get('skin_poses') as SkinPose[]; + let options = [] + let memory_list = StateMemory.get('skin_poses') as SkinPose[] memory_list.forEach((pose: SkinPose, i: number) => { let option = { name: pose.name, icon: 'accessibility', id: i.toString(), click() { - loadPose(pose.data); + loadPose(pose.data) }, children: [ - {icon: 'update', name: 'action.custom_skin_poses.update', description: 'action.custom_skin_poses.update.desc', click() { - pose.data = getPoseData(); - StateMemory.save('skin_poses'); - }}, - {icon: 'delete', name: 'generic.delete', click() { - memory_list.remove(pose); - StateMemory.save('skin_poses'); - }} - ] + { + icon: 'update', + name: 'action.custom_skin_poses.update', + description: 'action.custom_skin_poses.update.desc', + click() { + pose.data = getPoseData() + StateMemory.save('skin_poses') + }, + }, + { + icon: 'delete', + name: 'generic.delete', + click() { + memory_list.remove(pose) + StateMemory.save('skin_poses') + }, + }, + ], } - options.push(option); + options.push(option) }) - - options.push( - '_', - 'add_custom_skin_pose' - ); - return options; - } + + options.push('_', 'add_custom_skin_pose') + return options + }, }) new Action('add_custom_skin_pose', { icon: 'add', category: 'view', - condition: {formats: ['skin'], modes: ['pose']}, + condition: { formats: ['skin'], modes: ['pose'] }, click(e) { Blockbench.textPrompt('generic.name', 'new pose', value => { let pose: SkinPose = { name: value, - data: getPoseData() - }; - let memory_list = StateMemory.get('skin_poses') as SkinPose[]; - memory_list.push(pose); - StateMemory.save('skin_poses'); + data: getPoseData(), + } + let memory_list = StateMemory.get('skin_poses') as SkinPose[] + memory_list.push(pose) + StateMemory.save('skin_poses') }) - } + }, }) }) // @ts-ignore -Interface.definePanels(function() { +Interface.definePanels(function () { new Panel('skin_pose', { icon: 'icon-player', - condition: {modes: ['pose']}, + condition: { modes: ['pose'] }, default_position: { slot: 'right_bar', float_position: [0, 0], float_size: [300, 80], - height: 80 + height: 80, }, toolbars: [ new Toolbar('skin_pose', { - children: [ - 'custom_skin_poses', - 'add_custom_skin_pose' - ] - }) + children: ['custom_skin_poses', 'add_custom_skin_pose'], + }), ], component: { - data() {return { - pose: 'default' - }}, + data() { + return { + pose: 'default', + } + }, methods: { setPose(pose) { - setDefaultPose(pose); - } + setDefaultPose(pose) + }, }, template: `
@@ -815,8 +883,8 @@ Interface.definePanels(function() {
  • - ` - } + `, + }, }) }) @@ -905,8 +973,8 @@ skin_presets.steve = { ] } ] - }` -}; + }`, +} skin_presets.alex = { display_name: 'Player - Slim', pose: true, @@ -1069,8 +1137,8 @@ skin_presets.alex = { ] } ] - }` -}; + }`, +} skin_presets.flat_texture = { display_name: 'Texture', @@ -1095,8 +1163,8 @@ skin_presets.flat_texture = { ] } ] - }` -}; + }`, +} skin_presets.block = { display_name: 'Block', model: `{ @@ -1123,8 +1191,8 @@ skin_presets.block = { ] } ] - }` -}; + }`, +} skin_presets.allay = { display_name: 'Allay', @@ -1198,7 +1266,7 @@ skin_presets.allay = { ] } ] - }` + }`, } skin_presets.armadillo = { display_name: 'Armadillo', @@ -1289,7 +1357,7 @@ skin_presets.armadillo = { ] } ] - }` + }`, } skin_presets.armor_main = { display_name: 'Armor (Main)', @@ -1355,8 +1423,8 @@ skin_presets.armor_main = { ] } ] - }` -}; + }`, +} skin_presets.armor_leggings = { display_name: 'Armor (Leggings)', pose: true, @@ -1393,8 +1461,8 @@ skin_presets.armor_leggings = { ] } ] - }` -}; + }`, +} skin_presets.armor_stand = { display_name: 'Armor Stand', model: `{ @@ -1469,8 +1537,8 @@ skin_presets.armor_stand = { ] } ] - }` -}; + }`, +} skin_presets.axolotl = { display_name: 'Axolotl', model: `{ @@ -1574,8 +1642,8 @@ skin_presets.axolotl = { ] } ] - }` -}; + }`, +} skin_presets.bamboo_raft = { display_name: 'Bamboo Raft', model: `{ @@ -1612,8 +1680,8 @@ skin_presets.bamboo_raft = { ] } ] - }` -}; + }`, +} skin_presets.banner = { display_name: 'Banner', model: `{ @@ -1639,12 +1707,12 @@ skin_presets.banner = { ] } ] - }` -}; + }`, +} skin_presets.bat = { display_name: 'Bat', pose: true, - variants: { + variants: { new: { name: 'New', model: `{ @@ -1728,7 +1796,7 @@ skin_presets.bat = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -1810,10 +1878,10 @@ skin_presets.bat = { ] } ] - }` + }`, }, - } -}; + }, +} skin_presets.bed = { display_name: 'Bed', model_bedrock: `{ @@ -1893,8 +1961,8 @@ skin_presets.bed = { ] } ] - }` -}; + }`, +} skin_presets.bee = { display_name: 'Bee', model: `{ @@ -1967,8 +2035,8 @@ skin_presets.bee = { ] } ] - }` -}; + }`, +} skin_presets.bell = { display_name: 'Bell', model: `{ @@ -1986,8 +2054,8 @@ skin_presets.bell = { ] } ] - }` -}; + }`, +} skin_presets.blaze = { display_name: 'Blaze', model: `{ @@ -2091,8 +2159,8 @@ skin_presets.blaze = { ] } ] - }` -}; + }`, +} skin_presets.boat = { display_name: 'Boat', model: `{ @@ -2166,8 +2234,8 @@ skin_presets.boat = { ] } ] - }` -}; + }`, +} skin_presets.bogged = { display_name: 'Bogged', model: `{ @@ -2259,8 +2327,8 @@ skin_presets.bogged = { ] } ] - }` -}; + }`, +} skin_presets.bogged_layer = { display_name: 'Bogged/Stray Layer', model: `{ @@ -2326,8 +2394,8 @@ skin_presets.bogged_layer = { ] } ] - }` -}; + }`, +} skin_presets.breeze = { display_name: 'Breeze', model: `{ @@ -2373,7 +2441,7 @@ skin_presets.breeze = { ] } ] - }` + }`, } skin_presets.breeze_tornado = { display_name: 'Breeze Tornado', @@ -2416,7 +2484,7 @@ skin_presets.breeze_tornado = { ] } ] - }` + }`, } skin_presets.camel = { display_name: 'Camel', @@ -2549,7 +2617,7 @@ skin_presets.camel = { ] } ] - }` + }`, } skin_presets.cat = { display_name: 'Cat', @@ -2634,8 +2702,8 @@ skin_presets.cat = { ] } ] - }` -}; + }`, +} skin_presets.cape_elytra = { display_name: 'Cape + Elytra', model: `{ @@ -2674,8 +2742,8 @@ skin_presets.cape_elytra = { ] } ] - }` -}; + }`, +} skin_presets.chest = { display_name: 'Chest', model: `{ @@ -2695,8 +2763,8 @@ skin_presets.chest = { ] } ] - }` -}; + }`, +} skin_presets.chest_left = { display_name: 'Chest Left', model: `{ @@ -2716,8 +2784,8 @@ skin_presets.chest_left = { ] } ] - }` -}; + }`, +} skin_presets.chest_right = { display_name: 'Chest Right', model: `{ @@ -2737,8 +2805,8 @@ skin_presets.chest_right = { ] } ] - }` -}; + }`, +} skin_presets.chicken = { display_name: 'Chicken', variants: { @@ -2810,7 +2878,7 @@ skin_presets.chicken = { ] } ] - }` + }`, }, cold: { name: 'Cold', @@ -2881,10 +2949,10 @@ skin_presets.chicken = { ] } ] - }` - } - } -}; + }`, + }, + }, +} skin_presets.cod = { display_name: 'Cod', model: `{ @@ -2943,8 +3011,8 @@ skin_presets.cod = { "pivot": [0, 0, 0] } ] - }` -}; + }`, +} skin_presets.copper_golem = { display_name: 'Copper Golem', model: `{ @@ -3017,7 +3085,7 @@ skin_presets.copper_golem = { ] } ] - }` + }`, } skin_presets.cow = { display_name: 'Cow', @@ -3093,11 +3161,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, cold: { name: 'Cold', - model: `{ + model: `{ "name": "cow_cold", "external_textures": ["entity/cow/cow_cold.png"], "texturewidth": 64, @@ -3167,11 +3235,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, warm: { name: 'Warm', - model: `{ + model: `{ "name": "cow_warm", "external_textures": ["entity/cow/cow_warm.png"], "texturewidth": 64, @@ -3242,11 +3310,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, old: { name: 'Classic', - model: `{ + model: `{ "name": "cow", "texturewidth": 64, "textureheight": 32, @@ -3304,10 +3372,10 @@ skin_presets.cow = { ] } ] - }` - } - } -}; + }`, + }, + }, +} skin_presets.creaking = { display_name: 'Creaking', model: `{ @@ -3389,8 +3457,8 @@ skin_presets.creaking = { ] } ] - }` -}; + }`, +} skin_presets.creeper = { display_name: 'Creeper', model: `{ @@ -3451,8 +3519,8 @@ skin_presets.creeper = { ] } ] - }` -}; + }`, +} skin_presets.dolphin = { display_name: 'Dolphin', pose: true, @@ -3607,8 +3675,8 @@ skin_presets.dolphin = { ] } ] - }` -}; + }`, +} skin_presets.enderdragon = { display_name: 'Ender Dragon', pose: true, @@ -3968,8 +4036,8 @@ skin_presets.enderdragon = { ] } ] - }` -}; + }`, +} skin_presets.enderman = { display_name: 'Enderman', model: `{ @@ -4023,8 +4091,8 @@ skin_presets.enderman = { ] } ] - }` -}; + }`, +} skin_presets.endermite = { display_name: 'Endermite', model: `{ @@ -4065,8 +4133,8 @@ skin_presets.endermite = { ] } ] - }` -}; + }`, +} skin_presets.evocation_fang = { display_name: 'Evocation Fang', model: `{ @@ -4101,8 +4169,8 @@ skin_presets.evocation_fang = { ] } ] - }` -}; + }`, +} skin_presets.evoker = { display_name: 'Evoker', model: `{ @@ -4180,8 +4248,8 @@ skin_presets.evoker = { ] } ] - }` -}; + }`, +} skin_presets.fox = { display_name: 'Fox', model_bedrock: `{ @@ -4307,8 +4375,8 @@ skin_presets.fox = { ] } ] - }` -}; + }`, +} skin_presets.frog = { display_name: 'Frog', model: `{ @@ -4417,8 +4485,8 @@ skin_presets.frog = { ] } ] - }` -}; + }`, +} skin_presets.ghast = { display_name: 'Ghast', model: `{ @@ -4512,8 +4580,8 @@ skin_presets.ghast = { ] } ] - }` -}; + }`, +} skin_presets.goat = { display_name: 'Goat', model: `{ @@ -4580,8 +4648,8 @@ skin_presets.goat = { ] } ] - }` -}; + }`, +} skin_presets.guardian = { display_name: 'Guardian', model: `{ @@ -4747,8 +4815,8 @@ skin_presets.guardian = { ] } ] - }` -}; + }`, +} skin_presets.happy_ghast = { display_name: 'Happy Ghast', model: `{ @@ -4843,8 +4911,8 @@ skin_presets.happy_ghast = { ] } ] - }` -}; + }`, +} skin_presets.harness = { display_name: 'Happy Ghast Harness', model: `{ @@ -4870,8 +4938,8 @@ skin_presets.harness = { ] } ] - }` -}; + }`, +} skin_presets.hoglin = { display_name: 'Hoglin', model: `{ @@ -4946,8 +5014,8 @@ skin_presets.hoglin = { ] } ] - }` -}; + }`, +} skin_presets.horse = { display_name: 'Horse', model: `{ @@ -5112,8 +5180,8 @@ skin_presets.horse = { ] } ] - }` -}; + }`, +} skin_presets.irongolem = { display_name: 'Iron Golem', model: `{ @@ -5173,8 +5241,8 @@ skin_presets.irongolem = { ] } ] - }` -}; + }`, +} skin_presets.llama = { display_name: 'Llama', model: `{ @@ -5250,8 +5318,8 @@ skin_presets.llama = { ] } ] - }` -}; + }`, +} skin_presets.lavaslime = { display_name: 'Magma Cube', variants: { @@ -5290,7 +5358,7 @@ skin_presets.lavaslime = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -5377,10 +5445,10 @@ skin_presets.lavaslime = { ] } ] - }` - } - } -}; + }`, + }, + }, +} skin_presets.minecart = { display_name: 'Minecart', model: `{ @@ -5434,8 +5502,8 @@ skin_presets.minecart = { ] } ] - }` -}; + }`, +} skin_presets.panda = { display_name: 'Panda', model: `{ @@ -5495,8 +5563,8 @@ skin_presets.panda = { ] } ] - }` -}; + }`, +} skin_presets.parrot = { display_name: 'Parrot', model: `{ @@ -5565,8 +5633,8 @@ skin_presets.parrot = { ] } ] - }` -}; + }`, +} skin_presets.phantom = { display_name: 'Phantom', model: `{ @@ -5651,8 +5719,8 @@ skin_presets.phantom = { ] } ] - }` -}; + }`, +} skin_presets.pig = { display_name: 'Pig', variants: { @@ -5725,7 +5793,7 @@ skin_presets.pig = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -5786,10 +5854,10 @@ skin_presets.pig = { ] } ] - }` - } - } -}; + }`, + }, + }, +} skin_presets.piglin = { display_name: 'Piglin', model: `{ @@ -5875,8 +5943,8 @@ skin_presets.piglin = { "pivot": [6, 15, 1] } ] - }` -}; + }`, +} skin_presets.pillager = { display_name: 'Pillager', model: `{ @@ -5944,8 +6012,8 @@ skin_presets.pillager = { ] } ] - }` -}; + }`, +} skin_presets.polarbear = { display_name: 'Polarbear', model: `{ @@ -6003,8 +6071,8 @@ skin_presets.polarbear = { ] } ] - }` -}; + }`, +} skin_presets.pufferfish = { display_name: 'Pufferfish', model: `{ @@ -6266,8 +6334,8 @@ skin_presets.pufferfish = { ] } ] - }` -}; + }`, +} skin_presets.rabbit = { display_name: 'Rabbit', model: `{ @@ -6381,8 +6449,8 @@ skin_presets.rabbit = { ] } ] - }` -}; + }`, +} skin_presets.ravager = { display_name: 'Ravager', model: `{ @@ -6463,8 +6531,8 @@ skin_presets.ravager = { ] } ] - }` -}; + }`, +} skin_presets.salmon = { display_name: 'Salmon', model: `{ @@ -6539,8 +6607,8 @@ skin_presets.salmon = { ] } ] - }` -}; + }`, +} skin_presets.sheep = { display_name: 'Sheep', model: `{ @@ -6598,8 +6666,8 @@ skin_presets.sheep = { ] } ] - }` -}; + }`, +} skin_presets.shield = { display_name: 'Shield', model: `{ @@ -6617,8 +6685,8 @@ skin_presets.shield = { ] } ] - }` -}; + }`, +} skin_presets.shulker = { display_name: 'Shulker', model: `{ @@ -6651,8 +6719,8 @@ skin_presets.shulker = { ] } ] - }` -}; + }`, +} skin_presets.shulker_bullet = { display_name: 'Shulker Bullet', model: `{ @@ -6671,8 +6739,8 @@ skin_presets.shulker_bullet = { ] } ] - }` -}; + }`, +} skin_presets.silverfish = { display_name: 'Silverfish', model: `{ @@ -6761,8 +6829,8 @@ skin_presets.silverfish = { ] } ] - }` -}; + }`, +} skin_presets.skeleton = { display_name: 'Skeleton/Stray', model: `{ @@ -6831,8 +6899,8 @@ skin_presets.skeleton = { ] } ] - }` -}; + }`, +} skin_presets.slime = { display_name: 'Slime', model: `{ @@ -6862,8 +6930,8 @@ skin_presets.slime = { ] } ] - }` -}; + }`, +} skin_presets.sniffer = { display_name: 'Sniffer', model: `{ @@ -6980,7 +7048,7 @@ skin_presets.sniffer = { ] } ] - }` + }`, } skin_presets.snowgolem = { display_name: 'Snowgolem', @@ -7036,8 +7104,8 @@ skin_presets.snowgolem = { ] } ] - }` -}; + }`, +} skin_presets.spider = { display_name: 'Spider', model: `{ @@ -7131,8 +7199,8 @@ skin_presets.spider = { ] } ] - }` -}; + }`, +} skin_presets.spyglass = { display_name: 'Spyglass', model: `{ @@ -7150,7 +7218,7 @@ skin_presets.spyglass = { ] } ] - }` + }`, } skin_presets.squid = { display_name: 'Squid', @@ -7235,8 +7303,8 @@ skin_presets.squid = { ] } ] - }` -}; + }`, +} skin_presets.strider = { display_name: 'Strider', model: `{ @@ -7329,8 +7397,8 @@ skin_presets.strider = { ] } ] - }` -}; + }`, +} skin_presets.tadpole = { display_name: 'Tadpole', model: `{ @@ -7364,8 +7432,8 @@ skin_presets.tadpole = { ] } ] - }` -}; + }`, +} skin_presets.tropicalfish_a = { display_name: 'Tropicalfish A', model: `{ @@ -7409,8 +7477,8 @@ skin_presets.tropicalfish_a = { ] } ] - }` -}; + }`, +} skin_presets.tropicalfish_b = { display_name: 'Tropicalfish B', model: `{ @@ -7455,8 +7523,8 @@ skin_presets.tropicalfish_b = { ] } ] - }` -}; + }`, +} skin_presets.turtle = { display_name: 'Turtle', model_bedrock: `{ @@ -7582,8 +7650,8 @@ skin_presets.turtle = { ] } ] - }` -}; + }`, +} skin_presets.vex = { display_name: 'Vex', model: `{ @@ -7650,8 +7718,8 @@ skin_presets.vex = { ] } ] - }` -}; + }`, +} skin_presets.villager = { display_name: 'Villager (Old)', model: `{ @@ -7708,8 +7776,8 @@ skin_presets.villager = { ] } ] - }` -}; + }`, +} skin_presets.villager_v2 = { display_name: 'Villager (New)', model_java: `{ @@ -7862,8 +7930,8 @@ skin_presets.villager_v2 = { ] } ] - }` -}; + }`, +} skin_presets.vindicator = { display_name: 'Vindicator', model: `{ @@ -7935,8 +8003,8 @@ skin_presets.vindicator = { ] } ] - }` -}; + }`, +} skin_presets.warden = { display_name: 'Warden', model: `{ @@ -8033,7 +8101,7 @@ skin_presets.warden = { ] } ] - }` + }`, } skin_presets.witch = { display_name: 'Witch', @@ -8127,8 +8195,8 @@ skin_presets.witch = { ] } ] - }` -}; + }`, +} skin_presets.witherBoss = { display_name: 'Wither', model: `{ @@ -8189,8 +8257,8 @@ skin_presets.witherBoss = { ] } ] - }` -}; + }`, +} skin_presets.wolf = { display_name: 'Wolf', model: `{ @@ -8262,8 +8330,8 @@ skin_presets.wolf = { ] } ] - }` -}; + }`, +} skin_presets.zombie = { display_name: 'Zombie', pose: true, @@ -8390,8 +8458,8 @@ skin_presets.zombie = { ] } ] - }` -}; + }`, +} skin_presets.zombie_villager_1 = { display_name: 'Zombie Villager (Old)', model: `{ @@ -8451,8 +8519,8 @@ skin_presets.zombie_villager_1 = { ] } ] - }` -}; + }`, +} skin_presets.zombie_villager_2 = { display_name: 'Zombie Villager (New)', model_java: `{ @@ -8606,9 +8674,9 @@ skin_presets.zombie_villager_2 = { ] } ] - }` -}; + }`, +} for (let id in skin_presets) { - model_options[id] = skin_presets[id].display_name; + model_options[id] = skin_presets[id].display_name } diff --git a/js/io/share.ts b/js/io/share.ts index 3b64fc47c..bf3c7738b 100644 --- a/js/io/share.ts +++ b/js/io/share.ts @@ -1,83 +1,126 @@ -import { Blockbench } from "../api"; -import { Dialog } from "../interface/dialog"; -import { FormInputType } from "../interface/form"; -import { settings } from "../interface/settings"; -import { BARS } from "../interface/toolbars"; -import { tl } from "../languages"; -import { Mesh } from "../outliner/mesh"; -import { Outliner } from "../outliner/outliner"; -import { ReferenceImage } from "../preview/reference_images"; -import { capitalizeFirstLetter } from "../util/util"; -import { Codecs } from "./codec"; +import { Blockbench } from '../api' +import { Dialog } from '../interface/dialog' +import { FormInputType } from '../interface/form' +import { settings } from '../interface/settings' +import { BARS } from '../interface/toolbars' +import { tl } from '../languages' +import { Mesh } from '../outliner/mesh' +import { Outliner } from '../outliner/outliner' +import { ReferenceImage } from '../preview/reference_images' +import { capitalizeFirstLetter } from '../util/util' +import { Codecs } from './codec' -BARS.defineActions(function() { +BARS.defineActions(function () { function uploadSketchfabModel() { if (Outliner.elements.length === 0 || !Format) { - return; + return } - let tag_suggestions = ['low-poly', 'pixel-art', 'NoAI']; - if (Format.id !== 'free') tag_suggestions.push('minecraft'); - if (Format.id === 'skin') tag_suggestions.push('skin'); - if (!Mesh.all.length) tag_suggestions.push('voxel'); - let clean_project_name = Project.name.toLowerCase().replace(/[_.-]+/g, '-').replace(/[^a-z0-9-]+/, '').replace(/-geo/, ''); - if (Project.name) tag_suggestions.push(clean_project_name); - if (clean_project_name.includes('-')) tag_suggestions.safePush(...clean_project_name.split('-').filter(s => s.length > 2 && s != 'geo').reverse()); - + let tag_suggestions = ['low-poly', 'pixel-art', 'NoAI'] + if (Format.id !== 'free') tag_suggestions.push('minecraft') + if (Format.id === 'skin') tag_suggestions.push('skin') + if (!Mesh.all.length) tag_suggestions.push('voxel') + let clean_project_name = Project.name + .toLowerCase() + .replace(/[_.-]+/g, '-') + .replace(/[^a-z0-9-]+/, '') + .replace(/-geo/, '') + if (Project.name) tag_suggestions.push(clean_project_name) + if (clean_project_name.includes('-')) + tag_suggestions.safePush( + ...clean_project_name + .split('-') + .filter(s => s.length > 2 && s != 'geo') + .reverse() + ) + let categories = { - "": "-", - "animals-pets": "Animals & Pets", - "architecture": "Architecture", - "art-abstract": "Art & Abstract", - "cars-vehicles": "Cars & Vehicles", - "characters-creatures": "Characters & Creatures", - "cultural-heritage-history": "Cultural Heritage & History", - "electronics-gadgets": "Electronics & Gadgets", - "fashion-style": "Fashion & Style", - "food-drink": "Food & Drink", - "furniture-home": "Furniture & Home", - "music": "Music", - "nature-plants": "Nature & Plants", - "news-politics": "News & Politics", - "people": "People", - "places-travel": "Places & Travel", - "science-technology": "Science & Technology", - "sports-fitness": "Sports & Fitness", - "weapons-military": "Weapons & Military", + '': '-', + 'animals-pets': 'Animals & Pets', + architecture: 'Architecture', + 'art-abstract': 'Art & Abstract', + 'cars-vehicles': 'Cars & Vehicles', + 'characters-creatures': 'Characters & Creatures', + 'cultural-heritage-history': 'Cultural Heritage & History', + 'electronics-gadgets': 'Electronics & Gadgets', + 'fashion-style': 'Fashion & Style', + 'food-drink': 'Food & Drink', + 'furniture-home': 'Furniture & Home', + music: 'Music', + 'nature-plants': 'Nature & Plants', + 'news-politics': 'News & Politics', + people: 'People', + 'places-travel': 'Places & Travel', + 'science-technology': 'Science & Technology', + 'sports-fitness': 'Sports & Fitness', + 'weapons-military': 'Weapons & Military', } - + var dialog = new Dialog('sketchfab_uploader', { title: 'dialog.sketchfab_uploader.title', width: 640, form: { - token: {label: 'dialog.sketchfab_uploader.token', value: settings.sketchfab_token.value, type: 'password'}, - about_token: {type: 'info', text: tl('dialog.sketchfab_uploader.about_token', ['[sketchfab.com/settings/password](https://sketchfab.com/settings/password)'])}, - name: {label: 'dialog.sketchfab_uploader.name', value: capitalizeFirstLetter(Project.name.replace(/\..+/, '').replace(/[_.-]/g, ' '))}, - description: {label: 'dialog.sketchfab_uploader.description', type: 'textarea'}, - category1: {label: 'dialog.sketchfab_uploader.category', type: 'select', options: categories, value: ''}, - category2: {label: 'dialog.sketchfab_uploader.category2', type: 'select', options: categories, value: ''}, - tags: {label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2'}, - tag_suggestions: {label: 'dialog.sketchfab_uploader.suggested_tags', type: 'buttons', buttons: tag_suggestions, click(index) { - let {tags} = dialog.getFormResult(); - let new_tag = tag_suggestions[index]; - if (!(tags as string).split(/\s/g).includes(new_tag)) { - tags += ' ' + new_tag; - dialog.setFormValues({tags}); - } - }}, - animations: {label: 'dialog.sketchfab_uploader.animations', value: true, type: 'checkbox', condition: (Format.animation_mode && Animator.animations.length)}, - draft: {label: 'dialog.sketchfab_uploader.draft', type: 'checkbox', value: true}, + token: { + label: 'dialog.sketchfab_uploader.token', + value: settings.sketchfab_token.value, + type: 'password', + }, + about_token: { + type: 'info', + text: tl('dialog.sketchfab_uploader.about_token', [ + '[sketchfab.com/settings/password](https://sketchfab.com/settings/password)', + ]), + }, + name: { + label: 'dialog.sketchfab_uploader.name', + value: capitalizeFirstLetter( + Project.name.replace(/\..+/, '').replace(/[_.-]/g, ' ') + ), + }, + description: { label: 'dialog.sketchfab_uploader.description', type: 'textarea' }, + category1: { + label: 'dialog.sketchfab_uploader.category', + type: 'select', + options: categories, + value: '', + }, + category2: { + label: 'dialog.sketchfab_uploader.category2', + type: 'select', + options: categories, + value: '', + }, + tags: { label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2' }, + tag_suggestions: { + label: 'dialog.sketchfab_uploader.suggested_tags', + type: 'buttons', + buttons: tag_suggestions, + click(index) { + let { tags } = dialog.getFormResult() + let new_tag = tag_suggestions[index] + if (!(tags as string).split(/\s/g).includes(new_tag)) { + tags += ' ' + new_tag + dialog.setFormValues({ tags }) + } + }, + }, + animations: { + label: 'dialog.sketchfab_uploader.animations', + value: true, + type: 'checkbox', + condition: Format.animation_mode && Animator.animations.length, + }, + draft: { label: 'dialog.sketchfab_uploader.draft', type: 'checkbox', value: true }, divider: '_', - private: {label: 'dialog.sketchfab_uploader.private', type: 'checkbox'}, - password: {label: 'dialog.sketchfab_uploader.password', type: 'password'}, + private: { label: 'dialog.sketchfab_uploader.private', type: 'checkbox' }, + password: { label: 'dialog.sketchfab_uploader.password', type: 'password' }, }, onConfirm(formResult) { - if (!formResult.token || !formResult.name) { Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800) - return; + return } if (!(formResult.tags as string).split(' ').includes('blockbench')) { - formResult.tags += ' blockbench'; + formResult.tags += ' blockbench' } var data = new FormData() data.append('token', formResult.token as string) @@ -89,23 +132,24 @@ BARS.defineActions(function() { data.append('private', (formResult.private as boolean).toString()) data.append('password', formResult.password as string) data.append('source', 'blockbench') - + if (formResult.category1 || formResult.category2) { - let selected_categories: string[] = []; - if (formResult.category1) selected_categories.push(formResult.category1 as string); - if (formResult.category2) selected_categories.push(formResult.category2 as string); - data.append('categories', selected_categories.join(' ')); + let selected_categories: string[] = [] + if (formResult.category1) + selected_categories.push(formResult.category1 as string) + if (formResult.category2) + selected_categories.push(formResult.category2 as string) + data.append('categories', selected_categories.join(' ')) } - - settings.sketchfab_token.set(formResult.token); - - Codecs.gltf.compile({animations: formResult.animations}).then(content => { - - var blob = new Blob([content], {type: "text/plain;charset=utf-8"}); + + settings.sketchfab_token.set(formResult.token) + + Codecs.gltf.compile({ animations: formResult.animations }).then(content => { + var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }) var file = new File([blob], 'model.gltf') - + data.append('modelFile', file) - + $.ajax({ url: 'https://api.sketchfab.com/v3/models', data: data, @@ -113,18 +157,26 @@ BARS.defineActions(function() { contentType: false, processData: false, type: 'POST', - success: function(response) { + success: function (response) { let url = `https://sketchfab.com/models/${response.uid}` new Dialog('sketchfab_link', { title: tl('message.sketchfab.success'), icon: 'icon-sketchfab', form: { - message: {type: 'info', text: `[${formResult.name} on Sketchfab](${url})`}, - link: {type: 'text', value: url, readonly: true, share_text: true} - } - }).show(); + message: { + type: 'info', + text: `[${formResult.name} on Sketchfab](${url})`, + }, + link: { + type: 'text', + value: url, + readonly: true, + share_text: true, + }, + }, + }).show() }, - error: function(response) { + error: function (response) { let response_types = { [400]: 'Bad Request', [401]: 'Unauthorized', @@ -136,14 +188,18 @@ BARS.defineActions(function() { [408]: 'Request Timeout', [415]: 'Unsupported File Type', } - Blockbench.showQuickMessage(tl('message.sketchfab.error') + `: Error ${response.status} - ${response_types[response.status]||''}`, 1500) - console.error(response); - } + Blockbench.showQuickMessage( + tl('message.sketchfab.error') + + `: Error ${response.status} - ${response_types[response.status] || ''}`, + 1500 + ) + console.error(response) + }, }) }) - + dialog.hide() - } + }, }) dialog.show() } @@ -153,7 +209,7 @@ BARS.defineActions(function() { condition: () => Project && Outliner.elements.length, click() { uploadSketchfabModel() - } + }, }) new Action('share_model', { @@ -162,49 +218,65 @@ BARS.defineActions(function() { async click() { let thumbnail = await new Promise(resolve => { // @ts-ignore - Preview.selected.screenshot({width: 640, height: 480}, resolve); - }); - let image = new Image(); - image.src = thumbnail as string; - image.width = 320; - image.style.display = 'block'; - image.style.margin = 'auto'; - image.style.backgroundColor = 'var(--color-back)'; + Preview.selected.screenshot({ width: 640, height: 480 }, resolve) + }) + let image = new Image() + image.src = thumbnail as string + image.width = 320 + image.style.display = 'block' + image.style.margin = 'auto' + image.style.backgroundColor = 'var(--color-back)' var dialog = new Dialog({ id: 'share_model', title: 'dialog.share_model.title', form: { - name: {type: 'text', label: 'generic.name', value: Project.name}, - expire_time: {label: 'dialog.share_model.expire_time', type: 'select', default: '2d', options: { - '10m': tl('dates.minutes', [10]), - '1h': tl('dates.hour', [1]), - '1d': tl('dates.day', [1]), - '2d': tl('dates.days', [2]), - '1w': tl('dates.week', [1]), - '2w': tl('dates.weeks', [2]), - }}, - info: {type: 'info', text: 'The model and thumbnail will be stored on the Blockbench servers for the duration specified above. [Learn more](https://blockbench.net/blockbench-model-sharing-service/)'}, - reference_images: {type: 'checkbox', label: 'dialog.share_model.reference_images', value: true, condition: () => ReferenceImage.current_project.length}, - thumbnail: {type: 'checkbox', label: 'dialog.share_model.thumbnail', value: true}, + name: { type: 'text', label: 'generic.name', value: Project.name }, + expire_time: { + label: 'dialog.share_model.expire_time', + type: 'select', + default: '2d', + options: { + '10m': tl('dates.minutes', [10]), + '1h': tl('dates.hour', [1]), + '1d': tl('dates.day', [1]), + '2d': tl('dates.days', [2]), + '1w': tl('dates.week', [1]), + '2w': tl('dates.weeks', [2]), + }, + }, + info: { + type: 'info', + text: 'The model and thumbnail will be stored on the Blockbench servers for the duration specified above. [Learn more](https://blockbench.net/blockbench-model-sharing-service/)', + }, + reference_images: { + type: 'checkbox', + label: 'dialog.share_model.reference_images', + value: true, + condition: () => ReferenceImage.current_project.length, + }, + thumbnail: { + type: 'checkbox', + label: 'dialog.share_model.thumbnail', + value: true, + }, }, lines: [image], part_order: ['form', 'lines'], onFormChange(form) { - image.style.display = form.thumbnail ? 'block' : 'none'; + image.style.display = form.thumbnail ? 'block' : 'none' }, buttons: ['generic.share', 'dialog.cancel'], - onConfirm: function(formResult) { - - let name = formResult.name; - let expire_time = formResult.expire_time; + onConfirm: function (formResult) { + let name = formResult.name + let expire_time = formResult.expire_time let model = Codecs.project.compile({ compressed: false, absolute_paths: false, - reference_images: formResult.reference_images - }); - let data = {name, expire_time, model, thumbnail: undefined}; - if (formResult.thumbnail) data.thumbnail = thumbnail; + reference_images: formResult.reference_images, + }) + let data = { name, expire_time, model, thumbnail: undefined } + if (formResult.thumbnail) data.thumbnail = thumbnail $.ajax({ url: 'https://blckbn.ch/api/model', @@ -213,7 +285,7 @@ BARS.defineActions(function() { contentType: 'application/json; charset=utf-8', dataType: 'json', type: 'POST', - success: function(response) { + success: function (response) { let link = `https://blckbn.ch/${response.id}` new Dialog({ @@ -221,33 +293,40 @@ BARS.defineActions(function() { title: 'dialog.share_model.title', singleButton: true, form: { - link: {type: 'text', value: link, readonly: true, share_text: true} - } - }).show(); - + link: { + type: 'text', + value: link, + readonly: true, + share_text: true, + }, + }, + }).show() }, - error: function(response) { - let error_text = 'dialog.share_model.failed' + ' - ' + response.status; + error: function (response) { + let error_text = 'dialog.share_model.failed' + ' - ' + response.status if (response.status == 413) { - if (ReferenceImage.current_project.length && formResult.reference_images) { - error_text = 'dialog.share_model.too_large_references'; + if ( + ReferenceImage.current_project.length && + formResult.reference_images + ) { + error_text = 'dialog.share_model.too_large_references' } else { - error_text = 'dialog.share_model.too_large'; + error_text = 'dialog.share_model.too_large' } } Blockbench.showMessageBox({ title: tl('generic.error'), message: error_text, - icon: 'error' + icon: 'error', }) - console.error(response); - } + console.error(response) + }, }) - + dialog.hide() - } + }, }) dialog.show() - } + }, }) -}) \ No newline at end of file +}) diff --git a/js/languages.ts b/js/languages.ts index 1081c33d3..3124815bb 100644 --- a/js/languages.ts +++ b/js/languages.ts @@ -1,23 +1,23 @@ -import cz from '../lang/cz.json'; -import de from '../lang/de.json'; -import en from '../lang/en.json'; -import es from '../lang/es.json'; -import fr from '../lang/fr.json'; -import it from '../lang/it.json'; -import ja from '../lang/ja.json'; -import ko from '../lang/ko.json'; -import nl from '../lang/nl.json'; -import pl from '../lang/pl.json'; -import pt from '../lang/pt.json'; -import ru from '../lang/ru.json'; -import sv from '../lang/sv.json'; -import tr from '../lang/tr.json'; -import uk from '../lang/uk.json'; -import vi from '../lang/vi.json'; -import zh from '../lang/zh.json'; -import zh_tw from '../lang/zh_tw.json'; +import cz from '../lang/cz.json' +import de from '../lang/de.json' +import en from '../lang/en.json' +import es from '../lang/es.json' +import fr from '../lang/fr.json' +import it from '../lang/it.json' +import ja from '../lang/ja.json' +import ko from '../lang/ko.json' +import nl from '../lang/nl.json' +import pl from '../lang/pl.json' +import pt from '../lang/pt.json' +import ru from '../lang/ru.json' +import sv from '../lang/sv.json' +import tr from '../lang/tr.json' +import uk from '../lang/uk.json' +import vi from '../lang/vi.json' +import zh from '../lang/zh.json' +import zh_tw from '../lang/zh_tw.json' -type Language = Record; +type Language = Record export const data: Record = { cz: cz, de: de, @@ -37,7 +37,7 @@ export const data: Record = { vi: vi, zh: zh, zh_tw: zh_tw, -}; +} /** * Returns a translated string in the current language @@ -45,30 +45,34 @@ export const data: Record = { * @param variables Array of variables that replace anchors (%0, etc.) in the translation. Items can be strings or anything that can be converted to strings * @param default_value String value to default to if the translation is not available */ -export const tl = function(string: string, variables?: string | number | (string|number)[], default_value?: string): string { - if (string && string.length > 100) return string; +export const tl = function ( + string: string, + variables?: string | number | (string | number)[], + default_value?: string +): string { + if (string && string.length > 100) return string var result = Language.data[string] if (result && result.length > 0) { if (variables) { if (variables instanceof Array == false) { - variables = [variables]; + variables = [variables] } - var i = variables.length; + var i = variables.length while (i > 0) { - i--; - result = result.replace(new RegExp('%'+i, 'g'), variables[i]) + i-- + result = result.replace(new RegExp('%' + i, 'g'), variables[i]) } } - return result; + return result } else if (default_value != undefined) { - return default_value; + return default_value } else { //console.warn('Unable to find translation for key', string); - return string; + return string } } -export const translateUI = function() { - $('.tl').each(function(i, obj) { +export const translateUI = function () { + $('.tl').each(function (i, obj) { var text = tl($(obj).text()) $(obj).text(text) }) @@ -90,8 +94,8 @@ export const Language = { es: 'Espa\u00F1ol (Spanish)', fr: 'Fran\u00E7ais (French)', it: 'Italiano (Italian)', - ja: '\u65E5\u672C\u8A9E (Japanese)',//日本語 - ko: '\uD55C\uAD6D\uC5B4 (Korean)',//日本語 + ja: '\u65E5\u672C\u8A9E (Japanese)', //日本語 + ko: '\uD55C\uAD6D\uC5B4 (Korean)', //日本語 nl: 'Nederlands (Dutch)', pl: 'Polski (Polish)', pt: 'Portugu\u00EAs (Portuguese)', @@ -100,8 +104,8 @@ export const Language = { tr: 'Türkçe (Turkish)', uk: 'Українська (Ukrainian)', vi: 'Tiếng việt (Vietnamese)', - zh: '\u4e2d\u6587 (Chinese)',//中文 - zh_tw: '\u4E2D\u6587(\u81FA\u7063) (Traditional Chinese)',//中文(臺灣) + zh: '\u4e2d\u6587 (Chinese)', //中文 + zh_tw: '\u4E2D\u6587(\u81FA\u7063) (Traditional Chinese)', //中文(臺灣) }, /** * Add translations for custom translation strings @@ -110,17 +114,19 @@ export const Language = { */ addTranslations(language: string, strings: Record): void { for (var key in strings) { - if (language == Language.code || (language == 'en' && Language.data[key] == undefined)) { - Language.data[key] = strings[key]; + if ( + language == Language.code || + (language == 'en' && Language.data[key] == undefined) + ) { + Language.data[key] = strings[key] } } }, - toString: () => Language.code + toString: () => Language.code, } - // Get language code -let code; +let code try { code = JSON.parse(localStorage.getItem('settings')).language.value } catch (err) {} @@ -130,13 +136,12 @@ if (!code) { } if (code && Language.options[code]) { Language.code = code - document.body.parentElement.setAttribute('lang', Language.code); + document.body.parentElement.setAttribute('lang', Language.code) } - -Language.data = data[Language.code]; +Language.data = data[Language.code] Object.assign(window, { tl, - Language + Language, }) diff --git a/js/lib/CanvasFrame.ts b/js/lib/CanvasFrame.ts index 7b7b3850d..7b7d1a0fb 100644 --- a/js/lib/CanvasFrame.ts +++ b/js/lib/CanvasFrame.ts @@ -4,120 +4,125 @@ export class CanvasFrame { canvas: HTMLCanvasElement ctx: CanvasRenderingContext2D - + constructor() constructor(width: number, height: number) - constructor(source?: HTMLCanvasElement | HTMLImageElement | number, copy_canvas?: number | boolean) { + constructor( + source?: HTMLCanvasElement | HTMLImageElement | number, + copy_canvas?: number | boolean + ) { if (source instanceof HTMLCanvasElement) { if (source.getContext('2d') && copy_canvas !== true) { - this.canvas = source; + this.canvas = source } else { this.createCanvas(source.width, source.height) this.loadFromImage(source) } - } else if (source instanceof HTMLImageElement) { this.createCanvas(source.naturalWidth, source.naturalHeight) this.loadFromImage(source) - } else { - this.createCanvas(source || 16, typeof copy_canvas == 'number' ? copy_canvas : 16); + this.createCanvas(source || 16, typeof copy_canvas == 'number' ? copy_canvas : 16) } this.ctx = this.canvas.getContext('2d') } - get width() {return this.canvas.width;} - get height() {return this.canvas.height;} - + get width() { + return this.canvas.width + } + get height() { + return this.canvas.height + } + createCanvas(width: number, height: number) { - this.canvas = document.createElement('canvas'); - this.canvas.width = width; - this.canvas.height = height; + this.canvas = document.createElement('canvas') + this.canvas.width = width + this.canvas.height = height this.ctx = this.canvas.getContext('2d') } async loadFromURL(url: string) { let img = new Image() - img.src = url.replace(/#/g, '%23'); + img.src = url.replace(/#/g, '%23') await new Promise((resolve, reject) => { img.onload = () => { - this.loadFromImage(img); - resolve(); + this.loadFromImage(img) + resolve() } - img.onerror = reject; + img.onerror = reject }) } loadFromImage(img: HTMLImageElement | HTMLCanvasElement) { if ('naturalWidth' in img) { - this.canvas.width = img.naturalWidth; - this.canvas.height = img.naturalHeight; + this.canvas.width = img.naturalWidth + this.canvas.height = img.naturalHeight } this.ctx.drawImage(img, 0, 0) } loadFromCanvas(canvas: HTMLCanvasElement) { - this.canvas.width = canvas.width; - this.canvas.height = canvas.height; + this.canvas.width = canvas.width + this.canvas.height = canvas.height this.ctx.drawImage(canvas, 0, 0) } autoCrop() { // Based on code by remy, licensed under MIT // https://gist.github.com/remy/784508 - let copy = document.createElement('canvas').getContext('2d'); - let pixels = this.ctx.getImageData(0, 0, this.width, this.height); + let copy = document.createElement('canvas').getContext('2d') + let pixels = this.ctx.getImageData(0, 0, this.width, this.height) let bound = { - top: null as null|number, - left: null as null|number, - right: null as null|number, - bottom: null as null|number - }; - let x: number, y: number; - + top: null as null | number, + left: null as null | number, + right: null as null | number, + bottom: null as null | number, + } + let x: number, y: number + for (let i = 0; i < pixels.data.length; i += 4) { - if (pixels.data[i+3] !== 0) { - x = (i / 4) % this.width; - y = ~~((i / 4) / this.width); - + if (pixels.data[i + 3] !== 0) { + x = (i / 4) % this.width + y = ~~(i / 4 / this.width) + if (bound.top === null) { - bound.top = y; + bound.top = y } - + if (bound.left === null) { - bound.left = x; + bound.left = x } else if (x < bound.left) { - bound.left = x; + bound.left = x } - + if (bound.right === null) { - bound.right = x; + bound.right = x } else if (bound.right < x) { - bound.right = x; + bound.right = x } - + if (bound.bottom === null) { - bound.bottom = y; + bound.bottom = y } else if (bound.bottom < y) { - bound.bottom = y; + bound.bottom = y } } } - + let trimHeight = bound.bottom - bound.top + 1, trimWidth = bound.right - bound.left + 1, - trimmed = this.ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight); - - copy.canvas.width = trimWidth; - copy.canvas.height = trimHeight; - copy.putImageData(trimmed, 0, 0); - this.canvas = copy.canvas; - this.ctx = copy; + trimmed = this.ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight) + + copy.canvas.width = trimWidth + copy.canvas.height = trimHeight + copy.putImageData(trimmed, 0, 0) + this.canvas = copy.canvas + this.ctx = copy } isEmpty(): boolean { - let {data} = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); + let { data } = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) for (let i = 0; i < data.length; i += 4) { - let alpha = data[i+3]; - if (alpha) return false; + let alpha = data[i + 3] + if (alpha) return false } - return true; + return true } } -Object.assign(window, {CanvasFrame}); +Object.assign(window, { CanvasFrame }) diff --git a/js/lib/libs.ts b/js/lib/libs.ts index 57f9ebbf6..20c187fe2 100644 --- a/js/lib/libs.ts +++ b/js/lib/libs.ts @@ -1,6 +1,6 @@ import * as GIFEnc from 'gifenc' import $ from 'jquery' -import * as threejs from "three" +import * as threejs from 'three' import * as FIK from './fik' import Vue from 'vue/dist/vue.js' import JSZip from 'jszip' @@ -8,44 +8,44 @@ import Prism from 'prismjs' import GIF from 'gif.js' import vSortable from 'vue-sortable' import Sortable from 'sortablejs' -import {marked} from 'marked' +import { marked } from 'marked' import { APNGencoder } from './canvas2apng' import DOMPurify from 'dompurify' Vue.use(vSortable) Vue.directive('sortable', { - inserted: function (el, binding) { - new Sortable(el, binding.value || {}) - } + inserted: function (el, binding) { + new Sortable(el, binding.value || {}) + }, }) -const THREE = Object.assign({}, threejs); +const THREE = Object.assign({}, threejs) export { - GIFEnc, - GIF, - THREE, - $, - $ as jQuery, - FIK, - Vue, - JSZip, - Prism, - marked, - APNGencoder, - DOMPurify, + GIFEnc, + GIF, + THREE, + $, + $ as jQuery, + FIK, + Vue, + JSZip, + Prism, + marked, + APNGencoder, + DOMPurify, } Object.assign(window, { - GIFEnc, - GIF, - THREE, - jQuery: $, - $, - FIK, - Vue, - JSZip, - Prism, - marked, - APNGencoder, - DOMPurify, -}) \ No newline at end of file + GIFEnc, + GIF, + THREE, + jQuery: $, + $, + FIK, + Vue, + JSZip, + Prism, + marked, + APNGencoder, + DOMPurify, +}) diff --git a/js/main.ts b/js/main.ts index 644e870ef..082b516c4 100644 --- a/js/main.ts +++ b/js/main.ts @@ -1,124 +1,124 @@ //import { createApp } from 'vue' //import App from './App.vue' -import "./lib/libs" -import "./lib/jquery-ui.min" -import "./lib/targa" -import "./lib/VuePrismEditor.min" -import "./lib/molang-prism-syntax" -import "./lib/lzutf8" -import "./lib/spectrum.js" -import "./lib/color-picker.min" -import "./lib/GLTFExporter" -import "./lib/CanvasFrame" -import "./lib/canvas2apng" -import "./lib/easing" -import "./native_apis" -import "./preview/OrbitControls" +import './lib/libs' +import './lib/jquery-ui.min' +import './lib/targa' +import './lib/VuePrismEditor.min' +import './lib/molang-prism-syntax' +import './lib/lzutf8' +import './lib/spectrum.js' +import './lib/color-picker.min' +import './lib/GLTFExporter' +import './lib/CanvasFrame' +import './lib/canvas2apng' +import './lib/easing' +import './native_apis' +import './preview/OrbitControls' import './languages' -import "./util/util" -import "./util/json" -import "./util/three_custom" -import "./util/math_util" -import "./util/array_util" -import "./util/event_system" -import "./util/property" -import "./interface/menu" -import "./interface/actions" -import "./interface/shared_actions" -import "./interface/keyboard" -import "./misc" -import "./api" -import "./modes" -import "./file_system" -import "./interface/vue_components" -import "./interface/panels" -import "./interface/interface" -import "./interface/menu_bar" -import "./interface/start_screen" -import "./interface/form" -import "./interface/dialog" -import "./interface/keybinding" -import "./interface/settings" -import "./interface/about" -import "./interface/action_control" -import "./copy_paste" -import "./undo" +import './util/util' +import './util/json' +import './util/three_custom' +import './util/math_util' +import './util/array_util' +import './util/event_system' +import './util/property' +import './interface/menu' +import './interface/actions' +import './interface/shared_actions' +import './interface/keyboard' +import './misc' +import './api' +import './modes' +import './file_system' +import './interface/vue_components' +import './interface/panels' +import './interface/interface' +import './interface/menu_bar' +import './interface/start_screen' +import './interface/form' +import './interface/dialog' +import './interface/keybinding' +import './interface/settings' +import './interface/about' +import './interface/action_control' +import './copy_paste' +import './undo' -import './desktop.js'; +import './desktop.js' -import "./interface/setup_settings" -import "./interface/settings_window" -import "./edit_sessions" -import "./validator" -import "./outliner/outliner" -import "./outliner/element_panel" -import "./outliner/collections" -import "./outliner/group" -import "./outliner/mesh" -import "./outliner/cube" -import "./outliner/billboard" -import "./outliner/texture_mesh" -import "./outliner/armature" -import "./outliner/armature_bone" -import "./outliner/locator" -import "./outliner/null_object" -import "./outliner/spline_mesh" -import "./preview/preview" -import "./preview/reference_images" -import "./preview/screenshot" -import "./preview/canvas" -import "./interface/themes" -import "./modeling/edit" -import "./modeling/transform_gizmo" -import "./modeling/transform" -import "./modeling/scale" -import "./modeling/mesh_editing" -import "./modeling/mirror_modeling" -import "./modeling/spline_editing" -import "./modeling/weight_paint" -import "./texturing/textures" -import "./texturing/layers" -import "./texturing/texture_groups" -import "./texturing/texture_flipbook" -import "./texturing/uv" -import "./texturing/painter" -import "./texturing/texture_generator" -import "./texturing/edit_image" -import "./display_mode/display_mode" -import "./display_mode/attachable_preview" -import "./animations/animation_mode" -import "./animations/animation" -import "./animations/molang" -import "./animations/timeline_animators" -import "./animations/keyframe" -import "./animations/timeline" -import "./animations/animation_controllers" -import "./preview/preview_scenes" -import "./predicate_editor" -import "./plugin_loader" -import "./io/codec" -import "./io/format" -import "./io/project" -import "./io/io" -import "./io/share" -import "./texturing/color" -import "./io/formats/generic" -import "./io/formats/bbmodel" -import "./io/formats/java_block" -import "./io/formats/bedrock" -import "./io/formats/bedrock_old" -import "./io/formats/obj" -import "./io/formats/gltf" -import "./io/formats/fbx" -import "./io/formats/collada" -import "./io/formats/stl" -import "./io/formats/modded_entity" -import "./io/formats/optifine_jem" -import "./io/formats/optifine_jpm" -import "./io/formats/skin" -import "./io/formats/image" -import "./boot_loader" -import "./globals" -import "./global_types" +import './interface/setup_settings' +import './interface/settings_window' +import './edit_sessions' +import './validator' +import './outliner/outliner' +import './outliner/element_panel' +import './outliner/collections' +import './outliner/group' +import './outliner/mesh' +import './outliner/cube' +import './outliner/billboard' +import './outliner/texture_mesh' +import './outliner/armature' +import './outliner/armature_bone' +import './outliner/locator' +import './outliner/null_object' +import './outliner/spline_mesh' +import './preview/preview' +import './preview/reference_images' +import './preview/screenshot' +import './preview/canvas' +import './interface/themes' +import './modeling/edit' +import './modeling/transform_gizmo' +import './modeling/transform' +import './modeling/scale' +import './modeling/mesh_editing' +import './modeling/mirror_modeling' +import './modeling/spline_editing' +import './modeling/weight_paint' +import './texturing/textures' +import './texturing/layers' +import './texturing/texture_groups' +import './texturing/texture_flipbook' +import './texturing/uv' +import './texturing/painter' +import './texturing/texture_generator' +import './texturing/edit_image' +import './display_mode/display_mode' +import './display_mode/attachable_preview' +import './animations/animation_mode' +import './animations/animation' +import './animations/molang' +import './animations/timeline_animators' +import './animations/keyframe' +import './animations/timeline' +import './animations/animation_controllers' +import './preview/preview_scenes' +import './predicate_editor' +import './plugin_loader' +import './io/codec' +import './io/format' +import './io/project' +import './io/io' +import './io/share' +import './texturing/color' +import './io/formats/generic' +import './io/formats/bbmodel' +import './io/formats/java_block' +import './io/formats/bedrock' +import './io/formats/bedrock_old' +import './io/formats/obj' +import './io/formats/gltf' +import './io/formats/fbx' +import './io/formats/collada' +import './io/formats/stl' +import './io/formats/modded_entity' +import './io/formats/optifine_jem' +import './io/formats/optifine_jpm' +import './io/formats/skin' +import './io/formats/image' +import './boot_loader' +import './globals' +import './global_types' diff --git a/js/modeling/edit.ts b/js/modeling/edit.ts index 310d16fea..ba3edb901 100644 --- a/js/modeling/edit.ts +++ b/js/modeling/edit.ts @@ -1,6 +1,6 @@ -import { Mode } from "../modes"; +import { Mode } from '../modes' -BARS.defineActions(function() { +BARS.defineActions(function () { new Mode('edit', { icon: 'deployed_code', default_tool: 'move_tool', @@ -9,15 +9,17 @@ BARS.defineActions(function() { onSelect: () => { Outliner.elements.forEach(cube => { // @ts-ignore - if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube); + if (cube.preview_controller.updatePixelGrid) + cube.preview_controller.updatePixelGrid(cube) }) }, onUnselect: () => { - if (Undo) Undo.closeAmendEditMenu(); + if (Undo) Undo.closeAmendEditMenu() Outliner.elements.forEach(cube => { // @ts-ignore - if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube); + if (cube.preview_controller.updatePixelGrid) + cube.preview_controller.updatePixelGrid(cube) }) - } + }, }) }) diff --git a/js/modeling/mesh/attach_armature.ts b/js/modeling/mesh/attach_armature.ts index 39e649aff..83cb7e8c9 100644 --- a/js/modeling/mesh/attach_armature.ts +++ b/js/modeling/mesh/attach_armature.ts @@ -1,18 +1,17 @@ -import { Armature } from "../../outliner/armature"; -import { ArmatureBone } from "../../outliner/armature_bone"; -import { sameMeshEdge } from "./util"; -import { THREE } from "../../lib/libs"; -import { pointInPolygon } from "../../util/util"; -import { Blockbench } from "../../api"; - +import { Armature } from '../../outliner/armature' +import { ArmatureBone } from '../../outliner/armature_bone' +import { sameMeshEdge } from './util' +import { THREE } from '../../lib/libs' +import { pointInPolygon } from '../../util/util' +import { Blockbench } from '../../api' interface BoneInfo { - bone: ArmatureBone, - name: string, - tail_offset: THREE.Vector3, - start: THREE.Vector3, - end: THREE.Vector3, - line: THREE.Line3, + bone: ArmatureBone + name: string + tail_offset: THREE.Vector3 + start: THREE.Vector3 + end: THREE.Vector3 + line: THREE.Line3 _distance?: number _distance_on_line?: number _amount?: number @@ -28,23 +27,23 @@ interface EdgeLoop { } function calculateWeights(mesh: Mesh, armature: Armature) { - let armature_bones = armature.getAllBones(); - Undo.initEdit({elements: [mesh, ...armature_bones]}); - mesh.preview_controller.updateTransform(mesh); + let armature_bones = armature.getAllBones() + Undo.initEdit({ elements: [mesh, ...armature_bones] }) + mesh.preview_controller.updateTransform(mesh) if (armature) { - mesh.sortAllFaceVertices(); + mesh.sortAllFaceVertices() let bone_infos: BoneInfo[] = armature_bones.map(bone => { - let tail_offset = new THREE.Vector3(); - let tail_bone = bone.children[0]; + let tail_offset = new THREE.Vector3() + let tail_bone = bone.children[0] if (tail_bone) { - tail_offset.fromArray(tail_bone.position); + tail_offset.fromArray(tail_bone.position) } else { - tail_offset.y = bone.length; + tail_offset.y = bone.length } - - let start = bone.getWorldCenter(); - let end = bone.mesh.localToWorld(tail_offset); + + let start = bone.getWorldCenter() + let end = bone.mesh.localToWorld(tail_offset) let data: BoneInfo = { bone, name: bone.name, @@ -52,227 +51,264 @@ function calculateWeights(mesh: Mesh, armature: Armature) { start, end, line: new THREE.Line3(start, end), - }; - return data; - }); + } + return data + }) // Analyze geometry - const vertex_edge_loops: Record = {}; + const vertex_edge_loops: Record = {} for (let vkey in mesh.vertices) { - - if (!vertex_edge_loops[vkey]) vertex_edge_loops[vkey] = []; - if (vertex_edge_loops[vkey].length >= 4) continue; + if (!vertex_edge_loops[vkey]) vertex_edge_loops[vkey] = [] + if (vertex_edge_loops[vkey].length >= 4) continue getEdgeLoops(mesh, vkey).forEach(loop => { let coplanar_vertices = [ loop[0][0], loop[Math.floor(loop.length * 0.33)][0], loop[Math.floor(loop.length * 0.66)][0], - ]; - let coplanar_points = coplanar_vertices.map(vkey => new THREE.Vector3().fromArray(mesh.vertices[vkey])); - let plane = new THREE.Plane().setFromCoplanarPoints(coplanar_points[0], coplanar_points[1], coplanar_points[2]); - let plane_quaternion = new THREE.Quaternion().setFromUnitVectors(plane.normal, new THREE.Vector3(0, 1, 0)); + ] + let coplanar_points = coplanar_vertices.map(vkey => + new THREE.Vector3().fromArray(mesh.vertices[vkey]) + ) + let plane = new THREE.Plane().setFromCoplanarPoints( + coplanar_points[0], + coplanar_points[1], + coplanar_points[2] + ) + let plane_quaternion = new THREE.Quaternion().setFromUnitVectors( + plane.normal, + new THREE.Vector3(0, 1, 0) + ) - let polygon: ArrayVector2[] = []; - let vkeys: string[] = []; + let polygon: ArrayVector2[] = [] + let vkeys: string[] = [] loop.forEach((edge: MeshEdge) => { - let vkey2 = edge[0]; - let point = new THREE.Vector3().fromArray(mesh.vertices[vkey2]); - plane.projectPoint(point, point); - point.applyQuaternion(plane_quaternion); - polygon.push([point.x, point.z]); - vkeys.push(vkey2); - }); + let vkey2 = edge[0] + let point = new THREE.Vector3().fromArray(mesh.vertices[vkey2]) + plane.projectPoint(point, point) + point.applyQuaternion(plane_quaternion) + polygon.push([point.x, point.z]) + vkeys.push(vkey2) + }) - let edge_loop = { loop, plane_quaternion, vkeys, polygon, plane }; + let edge_loop = { loop, plane_quaternion, vkeys, polygon, plane } for (let vkey2 of vkeys) { if (!vertex_edge_loops[vkey2]?.length) { - vertex_edge_loops[vkey2] = [edge_loop]; + vertex_edge_loops[vkey2] = [edge_loop] } else { let match = vertex_edge_loops[vkey2].find(edge_loop2 => { - return edge_loop2.vkeys.length == edge_loop.vkeys.length && edge_loop2.vkeys.allAre(vkey3 => edge_loop.vkeys.includes(vkey3)); - }); - if (!match) vertex_edge_loops[vkey2].push(edge_loop); + return ( + edge_loop2.vkeys.length == edge_loop.vkeys.length && + edge_loop2.vkeys.allAre(vkey3 => edge_loop.vkeys.includes(vkey3)) + ) + }) + if (!match) vertex_edge_loops[vkey2].push(edge_loop) } } }) } - // Calculate base vertex weights - const vertex_main_bone: Record = {}; + const vertex_main_bone: Record = {} for (let vkey in mesh.vertices) { - let global_pos = new THREE.Vector3().fromArray(mesh.vertices[vkey]); - let edge_loops = vertex_edge_loops[vkey]; + let global_pos = new THREE.Vector3().fromArray(mesh.vertices[vkey]) + let edge_loops = vertex_edge_loops[vkey] - let shortest_edge_loop = edge_loops.findHighest((loop) => -loop.vkeys.length); + let shortest_edge_loop = edge_loops.findHighest(loop => -loop.vkeys.length) for (let bone_info of bone_infos) { - bone_info._is_inside = shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false; + bone_info._is_inside = + shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false - let closest_point = bone_info.line.closestPointToPoint(global_pos, true, new THREE.Vector3); - bone_info._distance = closest_point.distanceTo(global_pos); - bone_info.line.closestPointToPoint(global_pos, false, closest_point); - bone_info._distance_on_line = closest_point.distanceTo(global_pos); + let closest_point = bone_info.line.closestPointToPoint( + global_pos, + true, + new THREE.Vector3() + ) + bone_info._distance = closest_point.distanceTo(global_pos) + bone_info.line.closestPointToPoint(global_pos, false, closest_point) + bone_info._distance_on_line = closest_point.distanceTo(global_pos) } - - let inside_bones = bone_infos.filter(bone_infos => bone_infos._is_inside); - let bone_matches = inside_bones.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 1.2); + let inside_bones = bone_infos.filter(bone_infos => bone_infos._is_inside) + + let bone_matches = inside_bones.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 1.2 + ) if (!bone_matches.length) { - bone_matches = inside_bones.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 2); + bone_matches = inside_bones.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 2 + ) } - let full_match_bones = bone_matches.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 2); + let full_match_bones = bone_matches.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 2 + ) if (full_match_bones.length) { - let closest_bone = full_match_bones.findHighest(bone => -bone._distance); - vertex_main_bone[vkey] = closest_bone; - - closest_bone.bone.vertex_weights[vkey] = 1; + let closest_bone = full_match_bones.findHighest(bone => -bone._distance) + vertex_main_bone[vkey] = closest_bone + + closest_bone.bone.vertex_weights[vkey] = 1 } else { - bone_matches.sort((a, b) => a._distance - b._distance); - bone_matches = bone_matches.slice(0, 3); - vertex_main_bone[vkey] = bone_matches[0]; - let amount_sum = 0; + bone_matches.sort((a, b) => a._distance - b._distance) + bone_matches = bone_matches.slice(0, 3) + vertex_main_bone[vkey] = bone_matches[0] + let amount_sum = 0 for (let match of bone_matches) { - match._amount = Math.min(Math.max(match._distance_on_line, 0.04) / match._distance, 1); - amount_sum += match._amount; + match._amount = Math.min( + Math.max(match._distance_on_line, 0.04) / match._distance, + 1 + ) + amount_sum += match._amount } for (let match of bone_matches) { - match.bone.vertex_weights[vkey] = match._amount / amount_sum; + match.bone.vertex_weights[vkey] = match._amount / amount_sum } } } // Add smoothing for (let vkey in mesh.vertices) { - let closest_vertices = []; + let closest_vertices = [] for (let loop of vertex_edge_loops[vkey]) { - let index = loop.vkeys.indexOf(vkey); - closest_vertices.safePush(loop.vkeys.atWrapped(index+1)); - closest_vertices.safePush(loop.vkeys.atWrapped(index-1)); + let index = loop.vkeys.indexOf(vkey) + closest_vertices.safePush(loop.vkeys.atWrapped(index + 1)) + closest_vertices.safePush(loop.vkeys.atWrapped(index - 1)) } if (!vertex_main_bone[vkey]) { - let bones = []; + let bones = [] for (let vkey2 of closest_vertices) { - let bone = vertex_main_bone[vkey2]; + let bone = vertex_main_bone[vkey2] if (bone) { - bones.safePush(bone); - bone._weight = 0; + bones.safePush(bone) + bone._weight = 0 } } if (bones.length == 1) { - bones[0].bone.vertex_weights[vkey] = 1; - vertex_main_bone[vkey] = bones[0]; - continue; + bones[0].bone.vertex_weights[vkey] = 1 + vertex_main_bone[vkey] = bones[0] + continue } // Share between bones - let vertex_position = new THREE.Vector3().fromArray(mesh.vertices[vkey]); - let weight_sum = 0; + let vertex_position = new THREE.Vector3().fromArray(mesh.vertices[vkey]) + let weight_sum = 0 let weighted_vertices = closest_vertices.map(vkey2 => { - let distance = Reusable.vec1.fromArray(mesh.vertices[vkey2]).distanceTo(vertex_position) - weight_sum += 1 / distance; - return { distance, bone: vertex_main_bone[vkey2], vkey: vkey2, weight: 1 / distance }; + let distance = Reusable.vec1 + .fromArray(mesh.vertices[vkey2]) + .distanceTo(vertex_position) + weight_sum += 1 / distance + return { + distance, + bone: vertex_main_bone[vkey2], + vkey: vkey2, + weight: 1 / distance, + } }) for (let weighted of weighted_vertices) { - if (!weighted.bone) continue; - weighted.bone._weight += weighted.weight; + if (!weighted.bone) continue + weighted.bone._weight += weighted.weight } for (let bone of bones) { - bone.bone.vertex_weights[vkey] = 1; + bone.bone.vertex_weights[vkey] = 1 } } } } - Undo.finishEdit('Attach armature to mesh'); - Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true}}); + Undo.finishEdit('Attach armature to mesh') + Canvas.updateView({ elements: Mesh.selected, element_aspects: { geometry: true } }) } function isBoneInsideLoops(edge_loops: EdgeLoop[], bone_info: BoneInfo): THREE.Vector3 | false { for (let loop of edge_loops) { - let projected_point = loop.plane.intersectLine(bone_info.line, new THREE.Vector3()); - if (!projected_point) continue; - projected_point.applyQuaternion(loop.plane_quaternion); - let point = [projected_point.x, projected_point.z]; + let projected_point = loop.plane.intersectLine(bone_info.line, new THREE.Vector3()) + if (!projected_point) continue + projected_point.applyQuaternion(loop.plane_quaternion) + let point = [projected_point.x, projected_point.z] if (pointInPolygon(point, loop.polygon)) { - return projected_point; + return projected_point } } - return false; + return false } function getEdgeLoops(mesh: Mesh, start_vkey: string) { - - let vertices: string[] = []; - let edges: MeshEdge[] = []; + let vertices: string[] = [] + let edges: MeshEdge[] = [] - let processed_faces = []; + let processed_faces = [] function checkFace(face: MeshFace, side_vertices: MeshEdge) { - processed_faces.push(face); - let sorted_vertices = face.vertices.slice(); + processed_faces.push(face) + let sorted_vertices = face.vertices.slice() - let side_index_diff = sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]); - if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse(); + let side_index_diff = + sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]) + if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse() - let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey)); - let opposite_index_diff = sorted_vertices.indexOf(opposite_vertices[0]) - sorted_vertices.indexOf(opposite_vertices[1]); - if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse(); + let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey)) + let opposite_index_diff = + sorted_vertices.indexOf(opposite_vertices[0]) - + sorted_vertices.indexOf(opposite_vertices[1]) + if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse() - vertices.safePush(...side_vertices); - edges.push(side_vertices); + vertices.safePush(...side_vertices) + edges.push(side_vertices) // Find next (and previous) face function doNextFace(index: number) { for (let fkey in mesh.faces) { - let ref_face = mesh.faces[fkey]; - if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue; + let ref_face = mesh.faces[fkey] + if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue - let sorted_vertices = ref_face.vertices.slice(); - let vertices = ref_face.vertices.filter(vkey => vkey == side_vertices[index] || vkey == opposite_vertices[index]); + let sorted_vertices = ref_face.vertices.slice() + let vertices = ref_face.vertices.filter( + vkey => vkey == side_vertices[index] || vkey == opposite_vertices[index] + ) if (vertices.length >= 2) { let second_vertex = sorted_vertices.find((vkey, i) => { - return vkey !== side_vertices[index] - && vkey !== opposite_vertices[index] - && (sorted_vertices.length == 3 || Math.abs(sorted_vertices.indexOf(side_vertices[index]) - i) !== 2); + return ( + vkey !== side_vertices[index] && + vkey !== opposite_vertices[index] && + (sorted_vertices.length == 3 || + Math.abs(sorted_vertices.indexOf(side_vertices[index]) - i) !== 2) + ) }) - checkFace(ref_face, [side_vertices[index], second_vertex]); - break; + checkFace(ref_face, [side_vertices[index], second_vertex]) + break } } } - doNextFace(0); - doNextFace(1); + doNextFace(0) + doNextFace(1) } - let start_edges = []; + let start_edges = [] for (let fkey in mesh.faces) { - let face = mesh.faces[fkey]; - if (face.vertices.includes(start_vkey) == false) continue; + let face = mesh.faces[fkey] + if (face.vertices.includes(start_vkey) == false) continue for (let edge of face.getEdges()) { if (edge.includes(start_vkey) && !start_edges.find(e2 => sameMeshEdge(e2.edge, edge))) { - start_edges.push({edge, face}); + start_edges.push({ edge, face }) } } } - let loops: MeshEdge[][] = []; - start_edges.forEach(({edge, face}) => { - edges = []; - checkFace(face, edge); - if (edges.length > 1) loops.push(edges); - }); - return loops; + let loops: MeshEdge[][] = [] + start_edges.forEach(({ edge, face }) => { + edges = [] + checkFace(face, edge) + if (edges.length > 1) loops.push(edges) + }) + return loops } BARS.defineActions(() => { - new Action('calculate_vertex_weights', { icon: 'accessibility', condition: () => Mesh.selected[0]?.getArmature(), click(e) { - let mesh = Mesh.selected[0]; - calculateWeights(mesh, mesh.getArmature()); - } - }); -}) \ No newline at end of file + let mesh = Mesh.selected[0] + calculateWeights(mesh, mesh.getArmature()) + }, + }) +}) diff --git a/js/modeling/mesh/proportional_edit.ts b/js/modeling/mesh/proportional_edit.ts index f0934ba1c..82940f946 100644 --- a/js/modeling/mesh/proportional_edit.ts +++ b/js/modeling/mesh/proportional_edit.ts @@ -7,145 +7,162 @@ export const ProportionalEdit = { selection: '', }, calculateWeights(mesh: Mesh) { - if (!pe_toggle.value) return; - - let selected_vertices = mesh.getSelectedVertices(); - let {range, falloff, selection} = ProportionalEdit.config; - let linear_distance = selection == 'linear'; - - let all_mesh_connections: Record; + if (!pe_toggle.value) return + + let selected_vertices = mesh.getSelectedVertices() + let { range, falloff, selection } = ProportionalEdit.config + let linear_distance = selection == 'linear' + + let all_mesh_connections if (!linear_distance) { - all_mesh_connections = {}; + all_mesh_connections = {} for (let fkey in mesh.faces) { - let face = mesh.faces[fkey]; + let face = mesh.faces[fkey] face.getEdges().forEach(edge => { if (!all_mesh_connections[edge[0]]) { - all_mesh_connections[edge[0]] = [edge[1]]; + all_mesh_connections[edge[0]] = [edge[1]] } else { - all_mesh_connections[edge[0]].safePush(edge[1]); + all_mesh_connections[edge[0]].safePush(edge[1]) } if (!all_mesh_connections[edge[1]]) { - all_mesh_connections[edge[1]] = [edge[0]]; + all_mesh_connections[edge[1]] = [edge[0]] } else { - all_mesh_connections[edge[1]].safePush(edge[0]); + all_mesh_connections[edge[1]].safePush(edge[0]) } }) } } - ProportionalEdit.vertex_weights[mesh.uuid] = {}; - + ProportionalEdit.vertex_weights[mesh.uuid] = {} + for (let vkey in mesh.vertices) { - if (selected_vertices.includes(vkey)) continue; - - let distance = Infinity; + if (selected_vertices.includes(vkey)) continue + + let distance = Infinity if (linear_distance) { // Linear Distance selected_vertices.forEach(vkey2 => { - let pos1 = mesh.vertices[vkey]; - let pos2 = mesh.vertices[vkey2]; - let distance_square = Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2); + let pos1 = mesh.vertices[vkey] + let pos2 = mesh.vertices[vkey2] + let distance_square = + Math.pow(pos1[0] - pos2[0], 2) + + Math.pow(pos1[1] - pos2[1], 2) + + Math.pow(pos1[2] - pos2[2], 2) if (distance_square < distance) { - distance = distance_square; + distance = distance_square } }) - distance = Math.sqrt(distance); + distance = Math.sqrt(distance) } else { // Connection Distance - let found_match_depth = 0; - let scanned = []; - let frontier = [vkey]; - - depth_crawler: - for (let depth = 1; depth <= range; depth++) { - let new_frontier = []; + let found_match_depth = 0 + let scanned = [] + let frontier = [vkey] + + depth_crawler: for (let depth = 1; depth <= range; depth++) { + let new_frontier = [] for (let vkey1 of frontier) { - let connections = all_mesh_connections[vkey1]?.filter(vkey2 => !scanned.includes(vkey2)); - if (!connections || connections.length == 0) continue; - scanned.push(...connections); - new_frontier.push(...connections); + let connections = all_mesh_connections[vkey1]?.filter( + vkey2 => !scanned.includes(vkey2) + ) + if (!connections || connections.length == 0) continue + scanned.push(...connections) + new_frontier.push(...connections) } for (let vkey2 of new_frontier) { if (selected_vertices.includes(vkey2)) { - found_match_depth = depth; - break depth_crawler; + found_match_depth = depth + break depth_crawler } } - frontier = new_frontier; + frontier = new_frontier } if (found_match_depth) { - distance = found_match_depth; + distance = found_match_depth } } - if (distance > range) continue; - - let blend = 1 - (distance / (linear_distance ? range : range+1)); + if (distance > range) continue + + let blend = 1 - distance / (linear_distance ? range : range + 1) switch (falloff) { - case 'hermite_spline': blend = Math.hermiteBlend(blend); break; - case 'constant': blend = 1; break; + case 'hermite_spline': + blend = Math.hermiteBlend(blend) + break + case 'constant': + blend = 1 + break } - ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend; + ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend } }, - editVertices(mesh: Mesh, per_vertex: (vkey: string, blend: number) => void) { - if (!pe_toggle.value) return; + editVertices(mesh, per_vertex) { + if (!pe_toggle.value) return - let selected_vertices = mesh.getSelectedVertices(); + let selected_vertices = mesh.getSelectedVertices() for (let vkey in mesh.vertices) { - if (selected_vertices.includes(vkey)) continue; - - let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey]; - per_vertex(vkey, blend); + if (selected_vertices.includes(vkey)) continue + + let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey] + per_vertex(vkey, blend) } - } + }, } const pe_toggle = new Toggle('proportional_editing', { icon: 'wifi_tethering', category: 'edit', - condition: {modes: ['edit'], features: ['meshes']}, + condition: { modes: ['edit'], features: ['meshes'] }, tool_config: new ToolConfig('proportional_editing_options', { title: 'action.proportional_editing', width: 400, form: { - enabled: {type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false}, - range: {type: 'number', label: 'dialog.proportional_editing.range', value: 8}, - falloff: {type: 'select', label: 'dialog.proportional_editing.falloff', value: 'linear', options: { - linear: 'dialog.proportional_editing.falloff.linear', - hermite_spline: 'dialog.proportional_editing.falloff.hermite_spline', - constant: 'dialog.proportional_editing.falloff.constant', - }}, - selection: {type: 'select', label: 'dialog.proportional_editing.selection', value: 'linear', options: { - linear: 'dialog.proportional_editing.selection.linear', - connections: 'dialog.proportional_editing.selection.connections', - //path: 'Connection Path', - }}, + enabled: { type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false }, + range: { type: 'number', label: 'dialog.proportional_editing.range', value: 8 }, + falloff: { + type: 'select', + label: 'dialog.proportional_editing.falloff', + value: 'linear', + options: { + linear: 'dialog.proportional_editing.falloff.linear', + hermite_spline: 'dialog.proportional_editing.falloff.hermite_spline', + constant: 'dialog.proportional_editing.falloff.constant', + }, + }, + selection: { + type: 'select', + label: 'dialog.proportional_editing.selection', + value: 'linear', + options: { + linear: 'dialog.proportional_editing.selection.linear', + connections: 'dialog.proportional_editing.selection.connections', + //path: 'Connection Path', + }, + }, + }, + onOpen() { + this.setFormValues({ enabled: pe_toggle.value }) }, onFormChange(formResult) { if (pe_toggle.value != formResult.enabled) { - pe_toggle.trigger(); + pe_toggle.trigger() } - (BarItems.proportional_editing_range as NumSlider).update(); - } + ;(BarItems.proportional_editing_range as NumSlider).update() + }, }), - onChange(value) { - ProportionalEdit.config.enabled = value; - } }) // @ts-ignore -ProportionalEdit.config = (pe_toggle.tool_config as ToolConfig).options; - +ProportionalEdit.config = (pe_toggle.tool_config as ToolConfig).options new NumSlider('proportional_editing_range', { category: 'edit', - condition: {modes: ['edit'], features: ['meshes']}, + condition: { modes: ['edit'], features: ['meshes'] }, get() { return ProportionalEdit.config.range }, change(modify) { - ProportionalEdit.config.range = modify(ProportionalEdit.config.range); + ProportionalEdit.config.range = modify(ProportionalEdit.config.range) }, onAfter() { - pe_toggle.tool_config.save(); - } -}) \ No newline at end of file + pe_toggle.tool_config.save() + }, +}) diff --git a/js/modeling/mesh/set_vertex_weights.ts b/js/modeling/mesh/set_vertex_weights.ts index 69602ee08..a7f851d71 100644 --- a/js/modeling/mesh/set_vertex_weights.ts +++ b/js/modeling/mesh/set_vertex_weights.ts @@ -1,47 +1,51 @@ -import { Armature } from "../../outliner/armature"; -import { ArmatureBone } from "../../outliner/armature_bone"; - +import { Armature } from '../../outliner/armature' +import { ArmatureBone } from '../../outliner/armature_bone' new Action('set_vertex_weights', { icon: 'weight', - condition: {modes: ['edit'], method: () => (Mesh.selected[0]?.getArmature())}, + condition: { modes: ['edit'], method: () => Mesh.selected[0]?.getArmature() }, click() { - let mesh = Mesh.selected[0]; - let selected_vertices = mesh.getSelectedVertices(); - let armature = mesh.getArmature() as Armature; - let available_bones: ArmatureBone[] = armature.getAllBones(); - let bone_options = {}; + let mesh = Mesh.selected[0] + let selected_vertices = mesh.getSelectedVertices() + let armature = mesh.getArmature() as Armature + let available_bones: ArmatureBone[] = armature.getAllBones() + let bone_options = {} for (let bone of available_bones) { - bone_options[bone.uuid] = bone.name; + bone_options[bone.uuid] = bone.name } let affected_bones = available_bones.filter(bone => { - return selected_vertices.find(vkey => bone.vertex_weights[vkey]); - }); + return selected_vertices.find(vkey => bone.vertex_weights[vkey]) + }) // Todo: translations. Add way to configure multiple bones new Dialog('set_vertex_weights', { title: 'Set vertex weights', form: { - bone: {type: 'select', label: 'Bone', value: available_bones[0].name, options: bone_options}, - weight: {type: 'number', label: 'Weight', value: 1, min: 0, max: 1} + bone: { + type: 'select', + label: 'Bone', + value: available_bones[0].name, + options: bone_options, + }, + weight: { type: 'number', label: 'Weight', value: 1, min: 0, max: 1 }, }, onConfirm(result) { - let target_bone = available_bones.find(b => b.uuid == result.bone); - affected_bones.safePush(target_bone); + let target_bone = available_bones.find(b => b.uuid == result.bone) + affected_bones.safePush(target_bone) // @ts-ignore Should be fixed once converting armature_bone.js to ts - Undo.initEdit({elements: affected_bones}); + Undo.initEdit({ elements: affected_bones }) for (let bone of affected_bones) { - if (bone.uuid == result.bone) continue; + if (bone.uuid == result.bone) continue for (let vkey of selected_vertices) { - delete bone.vertex_weights[vkey]; + delete bone.vertex_weights[vkey] } } for (let vkey of selected_vertices) { - target_bone.vertex_weights[vkey] = result.weight; + target_bone.vertex_weights[vkey] = result.weight } - Undo.finishEdit('Set vertex weights'); - updateSelection(); - } - }).show(); - } -}) \ No newline at end of file + Undo.finishEdit('Set vertex weights') + updateSelection() + }, + }).show() + }, +}) diff --git a/js/modeling/mesh/util.ts b/js/modeling/mesh/util.ts index c5115fe54..8be54f0e7 100644 --- a/js/modeling/mesh/util.ts +++ b/js/modeling/mesh/util.ts @@ -1,3 +1,3 @@ export function sameMeshEdge(edge_a: MeshEdge, edge_b: MeshEdge): boolean { return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]) -} \ No newline at end of file +} diff --git a/js/modeling/mirror_modeling.ts b/js/modeling/mirror_modeling.ts index 13a5a8502..75f4d2cd1 100644 --- a/js/modeling/mirror_modeling.ts +++ b/js/modeling/mirror_modeling.ts @@ -1,53 +1,54 @@ -import { Blockbench } from "../api"; -import { ArmatureBone } from "../outliner/armature_bone"; -import { Billboard } from "../outliner/billboard"; -import { flipNameOnAxis } from "./transform"; +import { Blockbench } from '../api' +import { ArmatureBone } from '../outliner/armature_bone' +import { Billboard } from '../outliner/billboard' +import { flipNameOnAxis } from './transform' export const MirrorModeling = { initial_transformer_position: 0, outliner_snapshot: null, isCentered(element: OutlinerElement) { - let center = Format.centered_grid ? 0 : 8; - let element_type_options = MirrorModeling.element_types[element.type]; + let center = Format.centered_grid ? 0 : 8 + let element_type_options = MirrorModeling.element_types[element.type] if (element_type_options.isCentered) { - let result = element_type_options.isCentered(element, {center}); - if (result == false) return false; + let result = element_type_options.isCentered(element, { center }) + if (result == false) return false } if (element_type_options.check_parent_symmetry != false) { - let isAsymmetrical = (parent) => { + let isAsymmetrical = parent => { if (parent instanceof OutlinerNode) { - if ("origin" in parent && parent.origin[0] != center) return true; - if ("rotation" in parent && (parent.rotation[1] || parent.rotation[2])) return true; - return isAsymmetrical(parent.parent); + if ('origin' in parent && parent.origin[0] != center) return true + if ('rotation' in parent && (parent.rotation[1] || parent.rotation[2])) + return true + return isAsymmetrical(parent.parent) } } - if (isAsymmetrical(element.parent)) return false; + if (isAsymmetrical(element.parent)) return false } - return true; + return true }, createClone(original: OutlinerElement, undo_aspects: UndoAspects) { // Create or update clone - let options = (BarItems.mirror_modeling as Toggle).tool_config.options; - let mirror_uv = options.mirror_uv; - let center = Format.centered_grid ? 0 : 8; - let mirror_element = MirrorModeling.cached_elements[original.uuid]?.counterpart; - let element_type_options = MirrorModeling.element_types[original.type]; - let element_before_snapshot; + let options = (BarItems.mirror_modeling as Toggle).tool_config.options + let mirror_uv = options.mirror_uv + let center = Format.centered_grid ? 0 : 8 + let mirror_element = MirrorModeling.cached_elements[original.uuid]?.counterpart + let element_type_options = MirrorModeling.element_types[original.type] + let element_before_snapshot - if (mirror_element == original) return; + if (mirror_element == original) return if (mirror_element) { - element_before_snapshot = mirror_element.getUndoCopy(undo_aspects); - mirror_element.extend(original); - - mirror_element.flip(0, center); + element_before_snapshot = mirror_element.getUndoCopy(undo_aspects) + mirror_element.extend(original) + + mirror_element.flip(0, center) mirror_element.extend({ - name: element_before_snapshot.name - }); + name: element_before_snapshot.name, + }) if (!mirror_uv && element_type_options.maintainUV) { element_type_options.maintainUV(mirror_element, element_before_snapshot) } @@ -60,62 +61,82 @@ export const MirrorModeling = { // Update hierarchy up function updateParent(child, child_b) { - let parent = child.parent; - let parent_b = child_b.parent; - if (parent == parent_b) return; - if (parent.type != parent_b.type) return; - if (parent instanceof OutlinerNode == false || parent.getTypeBehavior('parent') != true) return; - if (parent_b instanceof OutlinerNode == false || parent_b.getTypeBehavior('parent') != true) return; + let parent = child.parent + let parent_b = child_b.parent + if (parent == parent_b) return + if (parent.type != parent_b.type) return + if ( + parent instanceof OutlinerNode == false || + parent.getTypeBehavior('parent') != true + ) + return + if ( + parent_b instanceof OutlinerNode == false || + parent_b.getTypeBehavior('parent') != true + ) + return - MirrorModeling.updateParentNodeCounterpart(parent_b, parent); + MirrorModeling.updateParentNodeCounterpart(parent_b, parent) - updateParent(parent, parent_b); + updateParent(parent, parent_b) } - updateParent(original, mirror_element); - + updateParent(original, mirror_element) } else { function getParentMirror(child: OutlinerNode) { - let parent = child.parent; - if (parent instanceof OutlinerNode == false) return 'root'; + let parent = child.parent + if (parent instanceof OutlinerNode == false) return 'root' - if ('origin' in parent && parent.origin[0] == center && MirrorModeling.isParentTreeSymmetrical(child, {center})) { - return parent; + if ( + 'origin' in parent && + parent.origin[0] == center && + MirrorModeling.isParentTreeSymmetrical(child, { center }) + ) { + return parent } else { - let mirror_group_parent = getParentMirror(parent) as OutlinerNode & OutlinerNodeParentTraits; + let mirror_group_parent = getParentMirror(parent) as OutlinerNode & + OutlinerNodeParentTraits // @ts-ignore - let mirror_group = new parent.constructor(parent); - - flipNameOnAxis(mirror_group, 0, (name: string) => true, parent.name); - mirror_group.origin[0] = MirrorModeling.flipCoord(mirror_group.origin[0]); - mirror_group.rotation[1] *= -1; - mirror_group.rotation[2] *= -1; - mirror_group.isOpen = parent.isOpen; - - let parent_list = mirror_group_parent instanceof OutlinerNode ? mirror_group_parent.children : Outliner.root; - let match = parent_list.find((node) => { - if (node instanceof OutlinerNode == false) return false; + let mirror_group = new parent.constructor(parent) + + flipNameOnAxis(mirror_group, 0, (name: string) => true, parent.name) + mirror_group.origin[0] = MirrorModeling.flipCoord(mirror_group.origin[0]) + mirror_group.rotation[1] *= -1 + mirror_group.rotation[2] *= -1 + mirror_group.isOpen = parent.isOpen + + let parent_list = + mirror_group_parent instanceof OutlinerNode + ? mirror_group_parent.children + : Outliner.root + let match = parent_list.find(node => { + if (node instanceof OutlinerNode == false) return false if ( - (node.name == mirror_group.name || Condition(mirror_group.getTypeBehavior('unique_name'))) && - ('rotation' in node && node.rotation instanceof Array && node.rotation.equals(mirror_group.rotation)) && - ('origin' in node && node.origin instanceof Array && node.origin.equals(mirror_group.origin)) + (node.name == mirror_group.name || + Condition(mirror_group.getTypeBehavior('unique_name'))) && + 'rotation' in node && + node.rotation instanceof Array && + node.rotation.equals(mirror_group.rotation) && + 'origin' in node && + node.origin instanceof Array && + node.origin.equals(mirror_group.origin) ) { - return true; + return true } }) if (match) { - return match; + return match } else { - mirror_group.createUniqueName(); - mirror_group.addTo(mirror_group_parent).init(); - return mirror_group; + mirror_group.createUniqueName() + mirror_group.addTo(mirror_group_parent).init() + return mirror_group } } } - let add_to = getParentMirror(original); + let add_to = getParentMirror(original) // @ts-ignore - mirror_element = new original.constructor(original); - mirror_element.addTo(add_to).init(); - mirror_element.flip(0, center); + mirror_element = new original.constructor(original) + mirror_element.addTo(add_to).init() + mirror_element.flip(0, center) if (element_type_options.updateCounterpart) { element_type_options.updateCounterpart(original, mirror_element, { @@ -125,136 +146,151 @@ export const MirrorModeling = { } } - MirrorModeling.insertElementIntoUndo(mirror_element, undo_aspects, element_before_snapshot); + MirrorModeling.insertElementIntoUndo(mirror_element, undo_aspects, element_before_snapshot) - let {preview_controller} = mirror_element; - preview_controller.updateAll(mirror_element); - return mirror_element; + let { preview_controller } = mirror_element + preview_controller.updateAll(mirror_element) + return mirror_element }, updateParentNodeCounterpart(node: OutlinerNode, original: OutlinerNode) { let keep_properties = { - name: node.name - }; - node.extend(original); - node.extend(keep_properties); + name: node.name, + } + node.extend(original) + node.extend(keep_properties) //flipNameOnAxis(node, 0, name => true, original.name); if ('origin' in node) { - node.origin[0] = MirrorModeling.flipCoord(node.origin[0]); + node.origin[0] = MirrorModeling.flipCoord(node.origin[0]) } if ('rotation' in node) { - node.rotation[1] *= -1; - node.rotation[2] *= -1; + node.rotation[1] *= -1 + node.rotation[2] *= -1 } }, getEditSide() { - return Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1; + return Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1 }, flipCoord(input: number): number { if (Format.centered_grid) { - return -input; + return -input } else { - return 16 - input; + return 16 - input } }, getMirrorElement(element: OutlinerElement): OutlinerElement | false { - let element_type_options = MirrorModeling.element_types[element.type]; - let center = Format.centered_grid ? 0 : 8; + let element_type_options = MirrorModeling.element_types[element.type] + let center = Format.centered_grid ? 0 : 8 if (element_type_options.getMirroredElement) { - return element_type_options.getMirroredElement(element, {center}) + return element_type_options.getMirroredElement(element, { center }) } - return false; + return false }, - isParentTreeSymmetrical(element: OutlinerNode, {center}) { - if (element.parent instanceof Group && Format.bone_rig == false) return true; - let parents = []; - let subject = element; - let symmetry_axes = [0]; - let off_axes = [1, 2]; + isParentTreeSymmetrical(element: OutlinerNode, { center }) { + if (element.parent instanceof Group && Format.bone_rig == false) return true + let parents = [] + let subject = element + let symmetry_axes = [0] + let off_axes = [1, 2] while (subject.parent instanceof OutlinerNode) { - subject = subject.parent; + subject = subject.parent parents.push(subject) } return parents.allAre(parent => { - if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false; - if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) return false; - return true; + if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false + if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) + return false + return true }) }, - insertElementIntoUndo(element: OutlinerElement, undo_aspects: UndoAspects, element_before_snapshot: any) { + insertElementIntoUndo( + element: OutlinerElement, + undo_aspects: UndoAspects, + element_before_snapshot: any + ) { // pre if (element_before_snapshot) { - if (!Undo.current_save.elements[element.uuid]) Undo.current_save.elements[element.uuid] = element_before_snapshot; + if (!Undo.current_save.elements[element.uuid]) + Undo.current_save.elements[element.uuid] = element_before_snapshot } else { - if (!Undo.current_save.outliner) Undo.current_save.outliner = MirrorModeling.outliner_snapshot; + if (!Undo.current_save.outliner) + Undo.current_save.outliner = MirrorModeling.outliner_snapshot } // post - if (!element_before_snapshot) undo_aspects.outliner = true; - undo_aspects.elements.safePush(element); + if (!element_before_snapshot) undo_aspects.outliner = true + undo_aspects.elements.safePush(element) }, element_types: {} as Record, registerElementType(type_class: any, options: MirrorModelingElementTypeOptions) { - new Property(type_class, 'boolean', 'allow_mirror_modeling', {default: true}); - let type = type_class.prototype.type; - MirrorModeling.element_types[type] = options; + new Property(type_class, 'boolean', 'allow_mirror_modeling', { default: true }) + let type = type_class.prototype.type + MirrorModeling.element_types[type] = options }, - cached_elements: {} + cached_elements: {}, } interface MirrorModelingElementTypeOptions { check_parent_symmetry?: boolean - isCentered?(element: OutlinerElement, options?: {center: number}): boolean - getMirroredElement?(element: OutlinerElement, options?: {center: number}): OutlinerElement | false + isCentered?(element: OutlinerElement, options?: { center: number }): boolean + getMirroredElement?( + element: OutlinerElement, + options?: { center: number } + ): OutlinerElement | false maintainUV?(element: OutlinerElement, original_data: any): void - discoverConnectionsPreEdit?(mesh: Mesh): {faces: Record, vertices: Record} + discoverConnectionsPreEdit?(mesh: Mesh): { + faces: Record + vertices: Record + } updateCounterpart?(original: OutlinerElement, counterpart: OutlinerElement, context: {}): void createLocalSymmetry?(element: OutlinerElement, cached_data: any): void } +Blockbench.on('init_edit', ({ aspects }) => { + if (!(BarItems.mirror_modeling as Toggle).value) return -Blockbench.on('init_edit', ({aspects}) => { - if (!(BarItems.mirror_modeling as Toggle).value) return; - - MirrorModeling.initial_transformer_position = Transformer.position.x; + MirrorModeling.initial_transformer_position = Transformer.position.x if (aspects.elements) { - MirrorModeling.cached_elements = {}; - MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON(); - let edit_side = MirrorModeling.getEditSide(); + MirrorModeling.cached_elements = {} + MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON() + let edit_side = MirrorModeling.getEditSide() - aspects.elements.forEach((element) => { + aspects.elements.forEach(element => { if (element.allow_mirror_modeling) { - let is_centered = MirrorModeling.isCentered(element); + let is_centered = MirrorModeling.isCentered(element) - let data = MirrorModeling.cached_elements[element.uuid] = { + let data = (MirrorModeling.cached_elements[element.uuid] = { is_centered, is_copy: false, counterpart: false as false | OutlinerElement, - pre_part_connections: undefined - }; + pre_part_connections: undefined, + }) if (!is_centered) { - data.is_copy = Math.sign(element.getWorldCenter().x) != edit_side; - data.counterpart = MirrorModeling.getMirrorElement(element); - if (!data.counterpart) data.is_copy = false; + data.is_copy = Math.sign(element.getWorldCenter().x) != edit_side + data.counterpart = MirrorModeling.getMirrorElement(element) + if (!data.counterpart) data.is_copy = false } else { if (MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit) { - data.pre_part_connections = MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit(element) + data.pre_part_connections = + MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit( + element + ) } } } }) } if (aspects.group || aspects.groups) { - if (!MirrorModeling.cached_elements) MirrorModeling.cached_elements = {}; - let selected_groups = aspects.groups ?? [aspects.group]; - MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON(); + if (!MirrorModeling.cached_elements) MirrorModeling.cached_elements = {} + let selected_groups = aspects.groups ?? [aspects.group] + MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON() // update undo - if (!Undo.current_save.outliner) Undo.current_save.outliner = Outliner.toJSON(); - aspects.outliner = true; + if (!Undo.current_save.outliner) Undo.current_save.outliner = Outliner.toJSON() + aspects.outliner = true selected_groups.forEach(group => { - if (group.origin[0] == (Format.centered_grid ? 0 : 8)) return; + if (group.origin[0] == (Format.centered_grid ? 0 : 8)) return let mirror_group = Group.all.find(g => { if ( @@ -263,98 +299,126 @@ Blockbench.on('init_edit', ({aspects}) => { Math.epsilon(group.origin[2], g.origin[2]) && group.getDepth() == g.getDepth() ) { - return true; + return true } }) if (mirror_group) { MirrorModeling.cached_elements[group.uuid] = { - counterpart: mirror_group + counterpart: mirror_group, } } }) } }) -Blockbench.on('finish_edit', ({aspects}) => { - if (!(BarItems.mirror_modeling as Toggle).value) return; +Blockbench.on('finish_edit', ({ aspects }) => { + if (!(BarItems.mirror_modeling as Toggle).value) return if (aspects.elements) { - aspects.elements = aspects.elements.slice(); - let static_elements_copy = aspects.elements.slice(); - static_elements_copy.forEach((element) => { + aspects.elements = aspects.elements.slice() + let static_elements_copy = aspects.elements.slice() + static_elements_copy.forEach(element => { let cached_data = MirrorModeling.cached_elements[element.uuid] if (element.allow_mirror_modeling && !element.locked) { - let is_centered = MirrorModeling.isCentered(element); + let is_centered = MirrorModeling.isCentered(element) - if (is_centered && MirrorModeling.element_types[element.type]?.createLocalSymmetry) { + if ( + is_centered && + MirrorModeling.element_types[element.type]?.createLocalSymmetry + ) { // Complete other side of mesh - MirrorModeling.element_types[element.type].createLocalSymmetry(element, cached_data); + MirrorModeling.element_types[element.type].createLocalSymmetry( + element, + cached_data + ) } if (is_centered) { - let mirror_element = cached_data?.counterpart; + let mirror_element = cached_data?.counterpart if (mirror_element && mirror_element.uuid != element.uuid) { - MirrorModeling.insertElementIntoUndo(mirror_element, Undo.current_save.aspects, mirror_element.getUndoCopy()); - mirror_element.remove(); - aspects.elements.remove(mirror_element); + MirrorModeling.insertElementIntoUndo( + mirror_element, + Undo.current_save.aspects, + mirror_element.getUndoCopy() + ) + mirror_element.remove() + aspects.elements.remove(mirror_element) } } else { // Construct clone at other side of model - MirrorModeling.createClone(element, aspects); + MirrorModeling.createClone(element, aspects) } } }) if (aspects.group || aspects.groups || aspects.outliner) { - Canvas.updateAllBones(); + Canvas.updateAllBones() } } else if (aspects.group || aspects.groups) { - let selected_groups = aspects.groups ?? [aspects.group]; + let selected_groups = aspects.groups ?? [aspects.group] selected_groups.forEach(group => { - let mirror_group = MirrorModeling.cached_elements[group.uuid]?.counterpart; + let mirror_group = MirrorModeling.cached_elements[group.uuid]?.counterpart if (mirror_group) { - MirrorModeling.updateParentNodeCounterpart(mirror_group, group); + MirrorModeling.updateParentNodeCounterpart(mirror_group, group) } }) - aspects.outliner = true; - Canvas.updateAllBones(); + aspects.outliner = true + Canvas.updateAllBones() } }) // Register element types MirrorModeling.registerElementType(Cube, { - isCentered(element: Cube, {center}) { - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - if (!Math.epsilon(element.to[0], MirrorModeling.flipCoord(element.from[0]), 0.01)) return false; - return true; + isCentered(element: Cube, { center }) { + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false + if (!Math.epsilon(element.to[0], MirrorModeling.flipCoord(element.from[0]), 0.01)) + return false + return true }, - getMirroredElement(element: Cube, {center}) { - let e = 0.01; - let symmetry_axes = [0]; - let off_axes = [1, 2]; + getMirroredElement(element: Cube, { center }) { + let e = 0.01 + let symmetry_axes = [0] + let off_axes = [1, 2] if ( - symmetry_axes.find((axis) => !Math.epsilon(element.from[axis]-center, center-element.to[axis], e)) == undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.from[axis] - center, center - element.to[axis], e) + ) == undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element; + return element } else { for (var element2 of Cube.all) { if ( element2 != element && Math.epsilon(element.inflate, element2.inflate, e) && - off_axes.find(axis => !Math.epsilon(element.from[axis], element2.from[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.to[axis], element2.to[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.size(axis), element2.size(axis), e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.to[axis]-center, center-element2.from[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.rotation[axis], element2.rotation[axis], e)) == undefined + off_axes.find( + axis => !Math.epsilon(element.from[axis], element2.from[axis], e) + ) == undefined && + off_axes.find(axis => !Math.epsilon(element.to[axis], element2.to[axis], e)) == + undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.size(axis), element2.size(axis), e) + ) == undefined && + symmetry_axes.find( + axis => + !Math.epsilon( + element.to[axis] - center, + center - element2.from[axis], + e + ) + ) == undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.rotation[axis], element2.rotation[axis], e) + ) == undefined ) { - return element2; + return element2 } } } - return false; + return false }, maintainUV(element: Cube, original_data) { element.extend({ @@ -362,55 +426,76 @@ MirrorModeling.registerElementType(Cube, { uv_offset: original_data.uv_offset, mirror_uv: original_data.mirror_uv, box_uv: original_data.box_uv, - autouv: original_data.autouv - }); - } + autouv: original_data.autouv, + }) + }, }) MirrorModeling.registerElementType(Mesh, { - isCentered(element: Mesh, {center}) { - if (Math.roundTo(element.origin[0], 3) != center) return false; - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - return true; + isCentered(element: Mesh, { center }) { + if (Math.roundTo(element.origin[0], 3) != center) return false + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false + return true }, - getMirroredElement(element: Mesh, {center}) { - let e = 0.01; - let symmetry_axes = [0]; - let off_axes = [ 1, 2]; - let ep = 0.5; - let this_center = element.getCenter(true); + getMirroredElement(element: Mesh, { center }) { + let e = 0.01 + let symmetry_axes = [0] + let off_axes = [1, 2] + let ep = 0.5 + let this_center = element.getCenter(true) if ( - symmetry_axes.find((axis) => !Math.epsilon(element.origin[axis], center, e)) == undefined && - symmetry_axes.find((axis) => !Math.epsilon(this_center[axis], center, ep)) == undefined && + symmetry_axes.find(axis => !Math.epsilon(element.origin[axis], center, e)) == + undefined && + symmetry_axes.find(axis => !Math.epsilon(this_center[axis], center, ep)) == undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element; + return element } else { for (var element2 of Mesh.all) { - let other_center = element2.getCenter(true); - if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) continue; + let other_center = element2.getCenter(true) + if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) + continue if ( element2 != element && - symmetry_axes.find(axis => !Math.epsilon(element.origin[axis]-center, center-element2.origin[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(this_center[axis]-center, center-other_center[axis], ep)) == undefined && - off_axes.find(axis => !Math.epsilon(element.origin[axis], element2.origin[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(this_center[axis], other_center[axis], ep)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.origin[axis] - center, + center - element2.origin[axis], + e + ) + ) == undefined && + symmetry_axes.find( + axis => + !Math.epsilon( + this_center[axis] - center, + center - other_center[axis], + ep + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.origin[axis], element2.origin[axis], e) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(this_center[axis], other_center[axis], ep) + ) == undefined ) { - return element2; + return element2 } } } - return false; + return false }, maintainUV(element: Mesh, original_data) { for (let fkey in element.faces) { - let face = element.faces[fkey]; - let face_before = original_data.faces[fkey]; + let face = element.faces[fkey] + let face_before = original_data.faces[fkey] if (face_before) { - face.texture = face_before.texture; + face.texture = face_before.texture for (let vkey of face_before.vertices) { if (face.vertices.includes(vkey) && face_before.uv[vkey]) { - face.uv[vkey] = face_before.uv[vkey].slice(); + face.uv[vkey] = face_before.uv[vkey].slice() } } } @@ -423,301 +508,350 @@ MirrorModeling.registerElementType(Mesh, { } // Detect vertex counterparts for (let vkey in mesh.vertices) { - if (data.vertices[vkey]) continue; - let vector = Reusable.vec1.fromArray(mesh.vertices[vkey]); - vector.x *= -1; + if (data.vertices[vkey]) continue + let vector = Reusable.vec1.fromArray(mesh.vertices[vkey]) + vector.x *= -1 for (let vkey2 in mesh.vertices) { //if (vkey == vkey2) continue; - let distance = vector.distanceTo(Reusable.vec2.fromArray(mesh.vertices[vkey2])); + let distance = vector.distanceTo(Reusable.vec2.fromArray(mesh.vertices[vkey2])) if (distance < 0.001) { - data.vertices[vkey] = vkey2; - data.vertices[vkey2] = vkey; - break; + data.vertices[vkey] = vkey2 + data.vertices[vkey2] = vkey + break } } } // Detect face counterparts for (let fkey in mesh.faces) { - if (data.faces[fkey]) continue; + if (data.faces[fkey]) continue for (let fkey2 in mesh.faces) { - if (fkey == fkey2) continue; + if (fkey == fkey2) continue let match = mesh.faces[fkey].vertices.allAre(vkey => { - let other_vkey = data.vertices[vkey]; - if (!other_vkey) return false; - return mesh.faces[fkey2].vertices.includes(other_vkey); + let other_vkey = data.vertices[vkey] + if (!other_vkey) return false + return mesh.faces[fkey2].vertices.includes(other_vkey) }) if (match) { - data.faces[fkey] = fkey2; - data.faces[fkey2] = fkey; - break; + data.faces[fkey] = fkey2 + data.faces[fkey2] = fkey + break } } } - return data; + return data }, createLocalSymmetry(mesh: Mesh, cached_data) { // Create or update clone - let edit_side = MirrorModeling.getEditSide(); - let options = (BarItems.mirror_modeling as Toggle).tool_config.options; - let mirror_uv = options.mirror_uv; - let pre_part_connections = cached_data?.pre_part_connections; + let edit_side = MirrorModeling.getEditSide() + let options = (BarItems.mirror_modeling as Toggle).tool_config.options + let mirror_uv = options.mirror_uv + let pre_part_connections = cached_data?.pre_part_connections // Delete all vertices on the non-edit side - let deleted_vertices = {}; - let deleted_vertices_by_position = {}; + let deleted_vertices = {} + let deleted_vertices_by_position = {} function positionKey(position) { - return position.map(p => Math.round(p*25)/25).join(','); + return position.map(p => Math.round(p * 25) / 25).join(',') } for (let vkey in mesh.vertices) { if (mesh.vertices[vkey][0] && Math.round(mesh.vertices[vkey][0] * edit_side * 50) < 0) { - deleted_vertices[vkey] = mesh.vertices[vkey]; - delete mesh.vertices[vkey]; - deleted_vertices_by_position[positionKey(deleted_vertices[vkey])] = vkey; + deleted_vertices[vkey] = mesh.vertices[vkey] + delete mesh.vertices[vkey] + deleted_vertices_by_position[positionKey(deleted_vertices[vkey])] = vkey } } // Copy existing vertices back to the non-edit side - let added_vertices = []; - let vertex_counterpart = {}; - let center_vertices = []; + let added_vertices = [] + let vertex_counterpart = {} + let center_vertices = [] for (let vkey in mesh.vertices) { - let vertex = mesh.vertices[vkey]; + let vertex = mesh.vertices[vkey] if (Math.abs(mesh.vertices[vkey][0]) < 0.02) { // On Edge - vertex_counterpart[vkey] = vkey; - center_vertices.push(vkey); + vertex_counterpart[vkey] = vkey + center_vertices.push(vkey) } else { - let position = [MirrorModeling.flipCoord(vertex[0]), vertex[1], vertex[2]] as ArrayVector3; - let vkey_new = deleted_vertices_by_position[positionKey(position)]; + let position = [ + MirrorModeling.flipCoord(vertex[0]), + vertex[1], + vertex[2], + ] as ArrayVector3 + let vkey_new = deleted_vertices_by_position[positionKey(position)] if (vkey_new) { - mesh.vertices[vkey_new] = position; + mesh.vertices[vkey_new] = position } else { - vkey_new = mesh.addVertices(position)[0]; + vkey_new = mesh.addVertices(position)[0] } - added_vertices.push(vkey_new); - vertex_counterpart[vkey] = vkey_new; + added_vertices.push(vkey_new) + vertex_counterpart[vkey] = vkey_new } } // Delete faces temporarily if all their vertices have been removed - let deleted_faces = {}; + let deleted_faces = {} for (let fkey in mesh.faces) { - let face = mesh.faces[fkey]; - let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey] || center_vertices.includes(vkey)); - if (deleted_face_vertices.length == face.vertices.length && !face.vertices.allAre(vkey => center_vertices.includes(vkey))) { - deleted_faces[fkey] = mesh.faces[fkey]; - delete mesh.faces[fkey]; + let face = mesh.faces[fkey] + let deleted_face_vertices = face.vertices.filter( + vkey => deleted_vertices[vkey] || center_vertices.includes(vkey) + ) + if ( + deleted_face_vertices.length == face.vertices.length && + !face.vertices.allAre(vkey => center_vertices.includes(vkey)) + ) { + deleted_faces[fkey] = mesh.faces[fkey] + delete mesh.faces[fkey] } } // Create mirrored faces - let original_fkeys = Object.keys(mesh.faces); + let original_fkeys = Object.keys(mesh.faces) for (let fkey of original_fkeys) { - let face = mesh.faces[fkey]; - let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey]); - if (deleted_face_vertices.length && face.vertices.length != deleted_face_vertices.length*2 && face.vertices.filter(vkey => center_vertices.includes(vkey)).length + deleted_face_vertices.length*2 != face.vertices.length) { + let face = mesh.faces[fkey] + let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey]) + if ( + deleted_face_vertices.length && + face.vertices.length != deleted_face_vertices.length * 2 && + face.vertices.filter(vkey => center_vertices.includes(vkey)).length + + deleted_face_vertices.length * 2 != + face.vertices.length + ) { // cannot flip. restore vertices instead? deleted_face_vertices.forEach(vkey => { - mesh.vertices[vkey] = deleted_vertices[vkey]; + mesh.vertices[vkey] = deleted_vertices[vkey] //delete deleted_vertices[vkey]; }) - } else if (deleted_face_vertices.length) { // face across zero line //let kept_face_keys = face.vertices.filter(vkey => mesh.vertices[vkey] != 0 && !deleted_face_vertices.includes(vkey)); - let new_counterparts = face.vertices.filter(vkey => !deleted_vertices[vkey]).map(vkey => vertex_counterpart[vkey]); + let new_counterparts = face.vertices + .filter(vkey => !deleted_vertices[vkey]) + .map(vkey => vertex_counterpart[vkey]) face.vertices.forEach((vkey, i) => { if (deleted_face_vertices.includes(vkey)) { // Across //let kept_key = kept_face_keys[i%kept_face_keys.length]; new_counterparts.sort((a, b) => { - let a_distance = Math.pow(mesh.vertices[a][1] - deleted_vertices[vkey][1], 2) + Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2); - let b_distance = Math.pow(mesh.vertices[b][1] - deleted_vertices[vkey][1], 2) + Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2); - return b_distance - a_distance; + let a_distance = + Math.pow(mesh.vertices[a][1] - deleted_vertices[vkey][1], 2) + + Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2) + let b_distance = + Math.pow(mesh.vertices[b][1] - deleted_vertices[vkey][1], 2) + + Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2) + return b_distance - a_distance }) - let counterpart = new_counterparts.pop(); + let counterpart = new_counterparts.pop() if (vkey != counterpart && counterpart) { - face.vertices.splice(i, 1, counterpart); - face.uv[counterpart] = face.uv[vkey].slice() as ArrayVector2; - delete face.uv[vkey]; + face.vertices.splice(i, 1, counterpart) + face.uv[counterpart] = face.uv[vkey].slice() as ArrayVector2 + delete face.uv[vkey] } } }) - - } else if (deleted_face_vertices.length == 0 && face.vertices.find((vkey) => vkey != vertex_counterpart[vkey])) { + } else if ( + deleted_face_vertices.length == 0 && + face.vertices.find(vkey => vkey != vertex_counterpart[vkey]) + ) { // Recreate face as mirrored - let new_face_key = pre_part_connections && pre_part_connections.faces[fkey]; - let original_face = deleted_faces[new_face_key]; + let new_face_key = pre_part_connections && pre_part_connections.faces[fkey] + let original_face = deleted_faces[new_face_key] - let new_face = new MeshFace(mesh, face); + let new_face = new MeshFace(mesh, face) face.vertices.forEach((vkey, i) => { - let new_vkey = vertex_counterpart[vkey]; - new_face.vertices.splice(i, 1, new_vkey); - delete new_face.uv[vkey]; + let new_vkey = vertex_counterpart[vkey] + new_face.vertices.splice(i, 1, new_vkey) + delete new_face.uv[vkey] if (mirror_uv || !original_face) { - new_face.uv[new_vkey] = face.uv[vkey].slice() as ArrayVector2; + new_face.uv[new_vkey] = face.uv[vkey].slice() as ArrayVector2 } else { // change - let original_vkey = pre_part_connections.vertices[vkey]; + let original_vkey = pre_part_connections.vertices[vkey] if (original_face.uv[original_vkey]) { - new_face.uv[new_vkey] = original_face.uv[original_vkey].slice(); + new_face.uv[new_vkey] = original_face.uv[original_vkey].slice() } } }) - new_face.invert(); + new_face.invert() if (new_face_key) { - mesh.faces[new_face_key] = new_face; + mesh.faces[new_face_key] = new_face } else { - [new_face_key] = mesh.addFaces(new_face); + ;[new_face_key] = mesh.addFaces(new_face) } } } - let selected_vertices = mesh.getSelectedVertices(true); - selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey])); - let selected_edges = mesh.getSelectedEdges(true); - selected_edges.replace(selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey]))); - let selected_faces = mesh.getSelectedFaces(true); - selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey])); - - let {preview_controller} = mesh; - preview_controller.updateGeometry(mesh); - preview_controller.updateFaces(mesh); - preview_controller.updateUV(mesh); - } + let selected_vertices = mesh.getSelectedVertices(true) + selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey])) + let selected_edges = mesh.getSelectedEdges(true) + selected_edges.replace( + selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey])) + ) + let selected_faces = mesh.getSelectedFaces(true) + selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey])) + + let { preview_controller } = mesh + preview_controller.updateGeometry(mesh) + preview_controller.updateFaces(mesh) + preview_controller.updateUV(mesh) + }, }) MirrorModeling.registerElementType(ArmatureBone, { - isCentered(element: ArmatureBone, {center}) { - if (Math.roundTo(element.position[0], 3) != center) return false; - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - return true; + isCentered(element: ArmatureBone, { center }) { + if (Math.roundTo(element.position[0], 3) != center) return false + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false + return true }, - getMirroredElement(element: ArmatureBone, {center}) { - let e = 0.01; - let symmetry_axes = [0]; - let off_axes = [1, 2]; + getMirroredElement(element: ArmatureBone, { center }) { + let e = 0.01 + let symmetry_axes = [0] + let off_axes = [1, 2] if ( - symmetry_axes.find((axis) => !Math.epsilon(element.position[axis], center, e)) == undefined && + symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == + undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element; + return element } else { for (let element2 of ArmatureBone.all) { - if (element == element2) continue; + if (element == element2) continue if ( - symmetry_axes.find(axis => !Math.epsilon(element.position[axis]-center, center-element2.position[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.position[axis], element2.position[axis], e)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.position[axis] - center, + center - element2.position[axis], + e + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.position[axis], element2.position[axis], e) + ) == undefined ) { - return element2; + return element2 } } } - return false; + return false }, createLocalSymmetry(element: ArmatureBone, cached_data) { - let edit_side = MirrorModeling.getEditSide(); - let options = (BarItems.mirror_modeling as Toggle).tool_config.options; + let edit_side = MirrorModeling.getEditSide() + let options = (BarItems.mirror_modeling as Toggle).tool_config.options // Update vertex weights on centered bones }, updateCounterpart(original, counterpart, context) { // Update vertex weights on off-centered bones - } + }, }) MirrorModeling.registerElementType(Billboard, { - isCentered(element: Billboard, {center}) { - if (Math.roundTo(element.position[0], 3) != center) return false; + isCentered(element: Billboard, { center }) { + if (Math.roundTo(element.position[0], 3) != center) return false //if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - return true; + return true }, - getMirroredElement(element: Billboard, {center}) { - let e = 0.01; - let symmetry_axes = [0]; - let off_axes = [1, 2]; + getMirroredElement(element: Billboard, { center }) { + let e = 0.01 + let symmetry_axes = [0] + let off_axes = [1, 2] if ( - symmetry_axes.find((axis) => !Math.epsilon(element.position[axis], center, e)) == undefined + symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == + undefined //off_axes.find(axis => element.rotation[axis]) == undefined ) { - return element; + return element } else { - for (let element2 of (Billboard.all as Billboard[])) { - if (element == element2) continue; + for (let element2 of Billboard.all as Billboard[]) { + if (element == element2) continue if ( - symmetry_axes.find(axis => !Math.epsilon(element.position[axis]-center, center-element2.position[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.position[axis], element2.position[axis], e)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.position[axis] - center, + center - element2.position[axis], + e + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.position[axis], element2.position[axis], e) + ) == undefined ) { - return element2; + return element2 } } } - return false; + return false }, maintainUV(element: Billboard, original_data) { element.extend({ faces: original_data.faces, - }); - } + }) + }, }) BARS.defineActions(() => { - let toggle = new Toggle('mirror_modeling', { icon: 'align_horizontal_center', category: 'edit', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, onChange() { - Project.mirror_modeling_enabled = this.value; - MirrorModeling.cached_elements = {}; - updateSelection(); + Project.mirror_modeling_enabled = this.value + MirrorModeling.cached_elements = {} + updateSelection() }, tool_config: new ToolConfig('mirror_modeling_options', { title: 'action.mirror_modeling', form: { - enabled: {type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false}, - mirror_uv: {type: 'checkbox', label: 'menu.mirror_modeling.mirror_uv', value: true} + enabled: { type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false }, + mirror_uv: { + type: 'checkbox', + label: 'menu.mirror_modeling.mirror_uv', + value: true, + }, }, onOpen() { - this.setFormValues({enabled: toggle.value}, false); + this.setFormValues({ enabled: toggle.value }, false) }, onFormChange(formResult) { if (toggle.value != formResult.enabled) { - toggle.trigger(); + toggle.trigger() } - } - }) + }, + }), }) let allow_toggle = new Toggle('allow_element_mirror_modeling', { icon: 'align_horizontal_center', category: 'edit', - condition: {modes: ['edit'], selected: {element: true}, method: () => toggle.value}, + condition: { modes: ['edit'], selected: { element: true }, method: () => toggle.value }, onChange(value) { Outliner.selected.forEach(element => { - if ('allow_mirror_modeling' in (element.constructor as any).properties == false) return; + if ('allow_mirror_modeling' in (element.constructor as any).properties == false) + return // @ts-ignore - element.allow_mirror_modeling = value; + element.allow_mirror_modeling = value }) - } + }, }) Blockbench.on('update_selection', () => { - if (!Condition(allow_toggle.condition)) return; + if (!Condition(allow_toggle.condition)) return // @ts-ignore - let disabled = Outliner.selected.find(el => el.allow_mirror_modeling === false); + let disabled = Outliner.selected.find(el => el.allow_mirror_modeling === false) if (allow_toggle.value != !disabled) { - allow_toggle.value = !disabled; - allow_toggle.updateEnabledState(); + allow_toggle.value = !disabled + allow_toggle.updateEnabledState() } }) new Action('apply_mirror_modeling', { icon: 'align_horizontal_right', category: 'edit', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, click() { - let value_before = toggle.value; - toggle.value = true; - Undo.initEdit({elements: Outliner.selected, groups: Group.selected}); - Undo.finishEdit('Applied mirror modeling'); - toggle.value = value_before; - } + let value_before = toggle.value + toggle.value = true + Undo.initEdit({ elements: Outliner.selected, groups: Group.selected }) + Undo.finishEdit('Applied mirror modeling') + toggle.value = value_before + }, }) }) -Object.assign(window, {MirrorModeling}); +Object.assign(window, { MirrorModeling }) diff --git a/js/modeling/weight_paint.ts b/js/modeling/weight_paint.ts index 4d46fad0d..5c5db2d26 100644 --- a/js/modeling/weight_paint.ts +++ b/js/modeling/weight_paint.ts @@ -1,60 +1,58 @@ -import { Blockbench } from '../api'; -import { THREE } from '../lib/libs'; -import { Armature } from '../outliner/armature'; -import { ArmatureBone } from '../outliner/armature_bone'; -import { Preview } from '../preview/preview'; - -type CanvasClickData = {event: MouseEvent} | { - event: MouseEvent - element: OutlinerElement - face: string - intersects: Array> -} - -let brush_outline: HTMLElement; +import { Blockbench } from '../api' +import { THREE } from '../lib/libs' +import { Armature } from '../outliner/armature' +import { ArmatureBone } from '../outliner/armature_bone' +import { Preview } from '../preview/preview' + +type CanvasClickData = + | { event: MouseEvent } + | { + event: MouseEvent + element: OutlinerElement + face: string + intersects: Array> + } + +let brush_outline: HTMLElement function updateBrushOutline(event: PointerEvent) { - if (!brush_outline) return; - let preview = Preview.selected as Preview; - let preview_offset = $(preview.canvas).offset(); - let click_pos = [ - event.clientX - preview_offset.left, - event.clientY - preview_offset.top, - ] - preview.node.append(brush_outline); - brush_outline.style.left = click_pos[0] + 'px'; - brush_outline.style.top = click_pos[1] + 'px'; + if (!brush_outline) return + let preview = Preview.selected as Preview + let preview_offset = $(preview.canvas).offset() + let click_pos = [event.clientX - preview_offset.left, event.clientY - preview_offset.top] + preview.node.append(brush_outline) + brush_outline.style.left = click_pos[0] + 'px' + brush_outline.style.top = click_pos[1] + 'px' } - -let screen_space_vertex_positions: null | Record = null; -const raycaster = new THREE.Raycaster(); +let screen_space_vertex_positions: null | Record = null +const raycaster = new THREE.Raycaster() function updateScreenSpaceVertexPositions(mesh: Mesh) { - if (screen_space_vertex_positions) return screen_space_vertex_positions; + if (screen_space_vertex_positions) return screen_space_vertex_positions + + const depth_check = (BarItems.weight_brush_xray as Toggle).value == false + let vec = new THREE.Vector3() + raycaster.ray.origin.setFromMatrixPosition(Preview.selected.camera.matrixWorld) + let raycasts = 0 - const depth_check = (BarItems.weight_brush_xray as Toggle).value == false; - let vec = new THREE.Vector3(); - raycaster.ray.origin.setFromMatrixPosition(Preview.selected.camera.matrixWorld); - let raycasts = 0; + screen_space_vertex_positions = {} - screen_space_vertex_positions = {}; - for (let vkey in mesh.vertices) { - let pos = mesh.mesh.localToWorld(vec.fromArray(mesh.vertices[vkey])); + let pos = mesh.mesh.localToWorld(vec.fromArray(mesh.vertices[vkey])) if (depth_check) { raycaster.ray.direction.copy(pos).sub(raycaster.ray.origin) - const z_distance = raycaster.ray.direction.length(); - raycaster.ray.direction.normalize(); - let intersection = raycaster.intersectObject(mesh.mesh, false)[0]; - raycasts++; - if (intersection && intersection.distance < z_distance-0.001) { - continue; + const z_distance = raycaster.ray.direction.length() + raycaster.ray.direction.normalize() + let intersection = raycaster.intersectObject(mesh.mesh, false)[0] + raycasts++ + if (intersection && intersection.distance < z_distance - 0.001) { + continue } } - let screen_pos = Preview.selected.vectorToScreenPosition(pos.clone()); - screen_space_vertex_positions[vkey] = screen_pos; + let screen_pos = Preview.selected.vectorToScreenPosition(pos.clone()) + screen_space_vertex_positions[vkey] = screen_pos } - return screen_space_vertex_positions; + return screen_space_vertex_positions } Blockbench.on('update_camera_position', () => { screen_space_vertex_positions = null @@ -69,133 +67,147 @@ new Tool('weight_brush', { transformerMode: 'hidden', selectElements: false, modes: ['edit'], - condition: {modes: ['edit'], method: () => Armature.all.length}, - + condition: { modes: ['edit'], method: () => Armature.all.length }, + onCanvasClick(data: CanvasClickData) { - if ('element' in data == false) return; - let preview = Preview.selected as Preview; - let preview_offset = $(preview.canvas).offset(); - let armature_bone = ArmatureBone.selected[0] as ArmatureBone; - let other_bones = armature_bone.getArmature().getAllBones() as ArmatureBone[]; - other_bones.remove(armature_bone); + if ('element' in data == false) return + let preview = Preview.selected as Preview + let preview_offset = $(preview.canvas).offset() + let armature_bone = ArmatureBone.selected[0] as ArmatureBone + let other_bones = armature_bone.getArmature().getAllBones() as ArmatureBone[] + other_bones.remove(armature_bone) if (!armature_bone) { - return Blockbench.showQuickMessage('Select an armature bone first!'); + return Blockbench.showQuickMessage('Select an armature bone first!') } if (data.element instanceof Mesh == false) { - return; + return } if (!data.element.getArmature()) { - return Blockbench.showQuickMessage('This mesh is not attached to an armature!'); + return Blockbench.showQuickMessage('This mesh is not attached to an armature!') } - let undo_tracked = [armature_bone]; - Undo.initEdit({elements: undo_tracked}); - - let last_click_pos = [0, 0]; - const draw = (event: MouseEvent, data?: CanvasClickData|false) => { - let radius = (BarItems.slider_weight_brush_size as NumSlider).get(); + let undo_tracked = [armature_bone] + Undo.initEdit({ elements: undo_tracked }) + + let last_click_pos = [0, 0] + const draw = (event: MouseEvent, data?: CanvasClickData | false) => { + let radius = (BarItems.slider_weight_brush_size as NumSlider).get() let click_pos = [ event.clientX - preview_offset.left, event.clientY - preview_offset.top, - ]; - let subtract = event.ctrlOrCmd || Pressing.overrides.ctrl; - if (Math.pow(last_click_pos[0]-click_pos[0], 2) + Math.pow(last_click_pos[1]-click_pos[1], 2) < 30) { - return; + ] + let subtract = event.ctrlOrCmd || Pressing.overrides.ctrl + if ( + Math.pow(last_click_pos[0] - click_pos[0], 2) + + Math.pow(last_click_pos[1] - click_pos[1], 2) < + 30 + ) { + return } - last_click_pos = click_pos; + last_click_pos = click_pos + + data = data ?? preview.raycast(event) + if (!data || 'element' in data == false) return + let mesh = data.element + if (mesh instanceof Mesh == false) return + let vec = new THREE.Vector2() - data = data ?? preview.raycast(event); - if (!data || 'element' in data == false) return; - let mesh = data.element; - if (mesh instanceof Mesh == false) return; - let vec = new THREE.Vector2(); + updateScreenSpaceVertexPositions(mesh) - updateScreenSpaceVertexPositions(mesh); - for (let vkey in mesh.vertices) { - let screen_pos = screen_space_vertex_positions[vkey]; - if (!screen_pos) continue; - let distance = vec.set(screen_pos.x - click_pos[0], screen_pos.y - click_pos[1]).length(); - let base_radius = 0.2; - let falloff = (1-(distance / radius)) * (1 + base_radius); - let influence = Math.hermiteBlend(Math.clamp(falloff, 0, 1)); - let value = armature_bone.vertex_weights[vkey] ?? 0; - + let screen_pos = screen_space_vertex_positions[vkey] + if (!screen_pos) continue + let distance = vec + .set(screen_pos.x - click_pos[0], screen_pos.y - click_pos[1]) + .length() + let base_radius = 0.2 + let falloff = (1 - distance / radius) * (1 + base_radius) + let influence = Math.hermiteBlend(Math.clamp(falloff, 0, 1)) + let value = armature_bone.vertex_weights[vkey] ?? 0 + if (event.shiftKey || Pressing.overrides.shift) { - influence /= 8; + influence /= 8 } if (subtract) { - value = value * (1-influence); + value = value * (1 - influence) } else { - value = value + (1-value) * influence; + value = value + (1 - value) * influence } // Reduce weight on other bones for (let bone of other_bones) { if (bone.vertex_weights[vkey] && !subtract) { - bone.vertex_weights[vkey] = Math.clamp(bone.vertex_weights[vkey] - influence, 0, 1); + bone.vertex_weights[vkey] = Math.clamp( + bone.vertex_weights[vkey] - influence, + 0, + 1 + ) if (Undo.current_save && !undo_tracked.includes(bone)) { - Undo.current_save.addElements([bone]); - undo_tracked.push(bone); + Undo.current_save.addElements([bone]) + undo_tracked.push(bone) } } } if (value < 0.04) { - delete armature_bone.vertex_weights[vkey]; + delete armature_bone.vertex_weights[vkey] } else { armature_bone.vertex_weights[vkey] = value } } // @ts-ignore - Mesh.preview_controller.updateGeometry(mesh); + Mesh.preview_controller.updateGeometry(mesh) } const stop = (event: MouseEvent) => { - document.removeEventListener('pointermove', draw); - document.removeEventListener('pointerup', stop); + document.removeEventListener('pointermove', draw) + document.removeEventListener('pointerup', stop) - Undo.finishEdit('Paint vertex weights'); + Undo.finishEdit('Paint vertex weights') } - document.addEventListener('pointermove', draw); - document.addEventListener('pointerup', stop); - draw(data.event, data); - + document.addEventListener('pointermove', draw) + document.addEventListener('pointerup', stop) + draw(data.event, data) }, onSelect() { - Canvas.updateView({elements: Mesh.all, element_aspects: {faces: true}}); - (BarItems.slider_weight_brush_size as NumSlider).update(); - Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); - Interface.addSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }) + ;(BarItems.slider_weight_brush_size as NumSlider).update() + Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.subtract') + Interface.addSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity') // @ts-ignore - ArmatureBone.preview_controller.material.wireframe = ArmatureBone.preview_controller.material_selected.wireframe = true; + ArmatureBone.preview_controller.material.wireframe = + ArmatureBone.preview_controller.material_selected.wireframe = true - brush_outline = Interface.createElement('div', {id: 'weight_brush_outline'}); - document.addEventListener('pointermove', updateBrushOutline); + brush_outline = Interface.createElement('div', { id: 'weight_brush_outline' }) + document.addEventListener('pointermove', updateBrushOutline) }, onUnselect() { setTimeout(() => { - Canvas.updateView({elements: Mesh.all, element_aspects: {faces: true}}); - }, 0); - Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); - Interface.removeSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }) + }, 0) + Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.subtract') + Interface.removeSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity') // @ts-ignore - ArmatureBone.preview_controller.material.wireframe = ArmatureBone.preview_controller.material_selected.wireframe = false; + ArmatureBone.preview_controller.material.wireframe = + ArmatureBone.preview_controller.material_selected.wireframe = false if (brush_outline) brush_outline.remove() - document.removeEventListener('pointermove', updateBrushOutline); - } + document.removeEventListener('pointermove', updateBrushOutline) + }, }) let slider = new NumSlider('slider_weight_brush_size', { condition: () => Toolbox?.selected?.id == 'weight_brush', tool_setting: 'weight_brush_size', category: 'edit', settings: { - min: 1, max: 1024, interval: 1, default: 50, - } + min: 1, + max: 1024, + interval: 1, + default: 50, + }, }) -slider.on('change', (data: {number: number}) => { +slider.on('change', (data: { number: number }) => { if (brush_outline) { - brush_outline.style.setProperty('--radius', data.number.toString()); + brush_outline.style.setProperty('--radius', data.number.toString()) } }) new Toggle('weight_brush_xray', { @@ -204,13 +216,17 @@ new Toggle('weight_brush_xray', { condition: () => Toolbox?.selected?.id == 'weight_brush', }) -const vertex_weight_view_modes = ['vertex_weight', 'weighted_bone_colors']; +const vertex_weight_view_modes = ['vertex_weight', 'weighted_bone_colors'] function updateWeightPreview() { - if (Toolbox.selected.id == 'weight_brush' || + if ( + Toolbox.selected.id == 'weight_brush' || vertex_weight_view_modes.includes(Project.view_mode) ) { - Canvas.updateView({elements: Mesh.all.filter(mesh => mesh.getArmature()), element_aspects: {geometry: true}}); - if (Modes.animate) Animator.preview(); + Canvas.updateView({ + elements: Mesh.all.filter(mesh => mesh.getArmature()), + element_aspects: { geometry: true }, + }) + if (Modes.animate) Animator.preview() } } -Blockbench.on('update_selection', updateWeightPreview); +Blockbench.on('update_selection', updateWeightPreview) diff --git a/js/modes.ts b/js/modes.ts index 2ff878f08..1458a9b08 100644 --- a/js/modes.ts +++ b/js/modes.ts @@ -1,11 +1,11 @@ -import { Vue } from "./lib/libs" -import { Blockbench } from "./api" -import { Interface, Panels } from "./interface/interface" -import { MenuBar } from "./interface/menu_bar" -import { updatePanelSelector, updateSidebarOrder } from "./interface/panels" -import { Prop } from "./misc" -import { Outliner } from "./outliner/outliner" -import { ReferenceImage } from "./preview/reference_images" +import { Vue } from './lib/libs' +import { Blockbench } from './api' +import { Interface, Panels } from './interface/interface' +import { MenuBar } from './interface/menu_bar' +import { updatePanelSelector, updateSidebarOrder } from './interface/panels' +import { Prop } from './misc' +import { Outliner } from './outliner/outliner' +import { ReferenceImage } from './preview/reference_images' interface ModeOptions { id?: string @@ -45,110 +45,117 @@ export class Mode extends KeybindItem { constructor(id: string, data: ModeOptions) { if (typeof id == 'object') { - data = id; - id = data.id; + data = id + id = data.id } // @ts-ignore super(id, data) - this.id = id; - this.name = data.name || tl('mode.'+this.id); - this.icon = data.icon || 'video_label'; + this.id = id + this.name = data.name || tl('mode.' + this.id) + this.icon = data.icon || 'video_label' this.selected = false - this.default_tool = data.default_tool; + this.default_tool = data.default_tool this.selectElements = data.selectElements !== false - this.hidden_node_types = data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : []; + this.hidden_node_types = + data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : [] this.hide_toolbars = data.hide_toolbars this.hide_sidebars = data.hide_sidebars this.hide_status_bar = data.hide_status_bar - this.condition = data.condition; - this.onSelect = data.onSelect; - this.onUnselect = data.onUnselect; + this.condition = data.condition + this.onSelect = data.onSelect + this.onUnselect = data.onUnselect - Modes.options[this.id] = this; + Modes.options[this.id] = this if (data.component) { - let node = document.createElement('div'); - let mount = document.createElement('div'); - node.id = 'mode_screen_' + this.id; - node.appendChild(mount); - document.getElementById('center').appendChild(node); + let node = document.createElement('div') + let mount = document.createElement('div') + node.id = 'mode_screen_' + this.id + node.appendChild(mount) + document.getElementById('center').appendChild(node) this.vue = new Vue(data.component) - this.vue.$mount(mount); + this.vue.$mount(mount) } } /**Selects the mode */ select() { if (Modes.selected instanceof Mode) { - Modes.selected.unselect(); + Modes.selected.unselect() } - this.selected = true; - Mode.selected = this; - Modes.selected = this; - Modes[Modes.selected.id] = true; - if (Project) Project.mode = this.id; + this.selected = true + Mode.selected = this + Modes.selected = this + Modes[Modes.selected.id] = true + if (Project) Project.mode = this.id - document.body.setAttribute('mode', this.id); + document.body.setAttribute('mode', this.id) if (MenuBar.mode_switcher_button) { - let icon = Blockbench.getIconNode(this.icon); - MenuBar.mode_switcher_button.firstChild.replaceWith(icon); - MenuBar.mode_switcher_button.classList.remove('hidden'); + let icon = Blockbench.getIconNode(this.icon) + MenuBar.mode_switcher_button.firstChild.replaceWith(icon) + MenuBar.mode_switcher_button.classList.remove('hidden') } - $('#main_toolbar .toolbar_wrapper').css('visibility', this.hide_toolbars ? 'hidden' : 'visible'); - $('#status_bar').css('display', this.hide_status_bar ? 'none' : 'flex'); + $('#main_toolbar .toolbar_wrapper').css( + 'visibility', + this.hide_toolbars ? 'hidden' : 'visible' + ) + $('#status_bar').css('display', this.hide_status_bar ? 'none' : 'flex') - Outliner.vue.options.hidden_types.replace(this.hidden_node_types); + Outliner.vue.options.hidden_types.replace(this.hidden_node_types) if (typeof this.onSelect === 'function') { this.onSelect() } - updatePanelSelector(); - ReferenceImage.updateAll(); - - if (Interface.Panels[Prop.active_panel] && !Condition(Interface.Panels[Prop.active_panel].condition)) { - Prop.active_panel = 'preview'; + updatePanelSelector() + ReferenceImage.updateAll() + + if ( + Interface.Panels[Prop.active_panel] && + !Condition(Interface.Panels[Prop.active_panel].condition) + ) { + Prop.active_panel = 'preview' } - - UVEditor.beforeMoving(); + + UVEditor.beforeMoving() if (!Blockbench.isMobile) { for (let id in Panels) { - let panel = Panels[id]; - panel.updatePositionData(); - panel.updateSlot(); - + let panel = Panels[id] + panel.updatePositionData() + panel.updateSlot() } - updateSidebarOrder(); + updateSidebarOrder() } Canvas.updateRenderSides() - let selected_tool = BarItems[this.tool] instanceof Tool && BarItems[this.tool]; - let default_tool = BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool]; + let selected_tool = BarItems[this.tool] instanceof Tool && BarItems[this.tool] + let default_tool = + BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool] if (selected_tool instanceof Tool && Condition(selected_tool.condition)) { - selected_tool.select(); + selected_tool.select() } else if (default_tool instanceof Tool) { - if (default_tool != Toolbox.selected) default_tool.select(); + if (default_tool != Toolbox.selected) default_tool.select() } else { - if (BarItems.move_tool != Toolbox.selected) (BarItems.move_tool as Tool).select(); + if (BarItems.move_tool != Toolbox.selected) (BarItems.move_tool as Tool).select() } - updateInterface(); - updateSelection(); - Blockbench.dispatchEvent('select_mode', {mode: this}) + updateInterface() + updateSelection() + Blockbench.dispatchEvent('select_mode', { mode: this }) } /**Unselects the mode */ unselect() { - delete Modes[this.id]; - Modes.previous_id = this.id; + delete Modes[this.id] + Modes.previous_id = this.id if (typeof this.onUnselect === 'function') { - Blockbench.dispatchEvent('unselect_mode', {mode: this}) + Blockbench.dispatchEvent('unselect_mode', { mode: this }) this.onUnselect() } - this.selected = false; - Mode.selected = Modes.selected = false; + this.selected = false + Mode.selected = Modes.selected = false } /**Activates the mode */ trigger() { @@ -158,11 +165,11 @@ export class Mode extends KeybindItem { } delete() { if (Mode.selected == this) { - Modes.options.edit.select(); + Modes.options.edit.select() } - delete Modes.options[this.id]; + delete Modes.options[this.id] } - static selected = null; + static selected = null } export const Modes = { get id() { @@ -178,48 +185,48 @@ export const Modes = { paint: false, pose: false, mobileModeMenu(button, event) { - let entries = []; + let entries = [] for (let id in Modes.options) { - let mode = Modes.options[id]; + let mode = Modes.options[id] let entry = { id, icon: mode.icon || 'mode', name: mode.name, condition: mode.condition, click: () => { - mode.select(); + mode.select() }, - }; - entries.push(entry); + } + entries.push(entry) } - let menu = new Menu(entries).open(button); - return menu; - } -}; -onVueSetup(function() { + let menu = new Menu(entries).open(button) + return menu + }, +} +onVueSetup(function () { if (!Blockbench.isMobile) { Modes.vue = new Vue({ el: '#mode_selector', data: { - options: Modes.options + options: Modes.options, }, methods: { showModes() { - let count = 0; + let count = 0 for (let key in this.options) { - if (Condition(this.options[key].condition)) count++; + if (Condition(this.options[key].condition)) count++ } - return count > 1; + return count > 1 }, - Condition - } + Condition, + }, }) } else { - document.getElementById('mode_selector').remove(); + document.getElementById('mode_selector').remove() } -}); +}) Object.assign(window, { Mode, - Modes -}); + Modes, +}) diff --git a/js/native_apis.ts b/js/native_apis.ts index e208e2110..5c7044d90 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -1,34 +1,48 @@ -import { BBPlugin } from "./plugin_loader"; -import { createScopedFS } from "./util/scoped_fs"; - -const electron: typeof import("@electron/remote") = require('@electron/remote'); -const {clipboard, shell, nativeImage, ipcRenderer, webUtils} = require('electron') as typeof import('electron'); -const app = electron.app; -const fs: typeof import("node:fs") = require('node:fs'); -const NodeBuffer: typeof import("node:buffer") = require('buffer'); -const zlib: typeof import("node:zlib") = require('zlib'); -const child_process: typeof import("node:child_process") = require('child_process'); -const https: typeof import("node:https") = require('https'); -const PathModule: typeof import("node:path") = require('path'); -const os: typeof import("node:os") = require('os'); -const currentwindow = electron.getCurrentWindow(); -const dialog = electron.dialog; +import { BBPlugin } from './plugin_loader' +import { createScopedFS } from './util/scoped_fs' + +const electron: typeof import('@electron/remote') = require('@electron/remote') +const { clipboard, shell, nativeImage, ipcRenderer, webUtils } = + require('electron') as typeof import('electron') +const app = electron.app +const fs: typeof import('node:fs') = require('node:fs') +const NodeBuffer: typeof import('node:buffer') = require('buffer') +const zlib: typeof import('node:zlib') = require('zlib') +const child_process: typeof import('node:child_process') = require('child_process') +const https: typeof import('node:https') = require('https') +const PathModule: typeof import('node:path') = require('path') +const os: typeof import('node:os') = require('os') +const currentwindow = electron.getCurrentWindow() +const dialog = electron.dialog /** @internal */ export { - electron, clipboard, shell, ipcRenderer, webUtils, - app, fs, NodeBuffer, zlib, child_process, https, PathModule, os, currentwindow, dialog, - Buffer, nativeImage + electron, + clipboard, + shell, + ipcRenderer, + webUtils, + app, + fs, + NodeBuffer, + zlib, + child_process, + https, + PathModule, + os, + currentwindow, + dialog, + Buffer, + nativeImage, } /** * @internal */ -export const process = window.process; -delete window.process; - +export const process = window.process +delete window.process -const { stringify, parse } = JSON; +const { stringify, parse } = JSON const SAFE_APIS = [ 'path', @@ -39,7 +53,7 @@ const SAFE_APIS = [ 'url', 'string_decoder', 'querystring', -]; +] const REQUESTABLE_APIS = [ 'fs', 'process', @@ -50,7 +64,7 @@ const REQUESTABLE_APIS = [ 'util', 'os', 'v8', -]; +] const API_DESCRIPTIONS = { fs: 'access and change files on your computer', process: 'access to the process running Blockbench', @@ -59,53 +73,58 @@ const API_DESCRIPTIONS = { os: 'see information about your computer', https: 'create servers and talk to other servers', dialog: 'open native dialogs', -}; +} type PluginPermissions = { - allowed: Record + allowed: Record } -const PLUGIN_SETTINGS_PATH = PathModule.join(app.getPath('userData'), 'plugin_permissions.json'); -const PluginSettings: Record = {}; +const PLUGIN_SETTINGS_PATH = PathModule.join(app.getPath('userData'), 'plugin_permissions.json') +const PluginSettings: Record = {} try { - let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, {encoding: 'utf-8'}); - let data = parse(content); + let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, { encoding: 'utf-8' }) + let data = parse(content) if (typeof data == 'object') { - Object.assign(PluginSettings, data); + Object.assign(PluginSettings, data) } } catch (err) {} function savePluginSettings() { - fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), {encoding: 'utf-8'}); + fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), { encoding: 'utf-8' }) } interface GetModuleOptions { scope?: string message?: string } -function getModule(module_name: string, plugin_id: string, plugin: InstanceType, options: GetModuleOptions = {}) { - const no_namespace_name = module_name.replace(/^node:/, ''); +function getModule( + module_name: string, + plugin_id: string, + plugin: InstanceType, + options: GetModuleOptions = {} +) { + const no_namespace_name = module_name.replace(/^node:/, '') if (SAFE_APIS.includes(no_namespace_name)) { - return originalRequire(module_name); + return originalRequire(module_name) } if (!REQUESTABLE_APIS.includes(no_namespace_name)) { - throw `The module "${module_name}" is not supported`; + throw `The module "${module_name}" is not supported` } - const options2: GetModuleOptions = {}; + const options2: GetModuleOptions = {} for (let key in options) { - options2[key] = options[key]; + options2[key] = options[key] } - let permission = PluginSettings[plugin_id]?.allowed[module_name]; - let has_permission = false; + let permission = PluginSettings[plugin_id]?.allowed[module_name] + let has_permission = false if (permission === true) { - has_permission = true; + has_permission = true } else if (module_name == 'fs' && permission?.directories?.includes(options2.scope)) { - has_permission = true; + has_permission = true } if (!has_permission) { - let api_description = API_DESCRIPTIONS[module_name] ?? `the module "${module_name}"`; - let option_text = ''; + let api_description = API_DESCRIPTIONS[module_name] ?? `the module "${module_name}"` + let option_text = '' if (module_name == 'fs' && options2.scope) { - api_description = 'a folder'; - option_text = '\nLocation: "' + options2.scope.replace(/\n/g, '') + '"'; + api_description = 'a folder' + option_text = '\nLocation: "' + options2.scope.replace(/\n/g, '') + '"' } let result = dialog.showMessageBoxSync(currentwindow, { @@ -115,84 +134,80 @@ function getModule(module_name: string, plugin_id: string, plugin: InstanceType< type: 'question', noLink: true, cancelId: 3, - buttons: [ - 'Allow once', - 'Always allow for this plugin', - 'Uninstall plugin', - 'Deny', - ] - }); + buttons: ['Allow once', 'Always allow for this plugin', 'Uninstall plugin', 'Deny'], + }) enum Result { Once = 0, Always = 1, Uninstall = 2, - Deny = 3 + Deny = 3, } if (result == Result.Always) { // Save permission if (!PluginSettings[plugin_id]?.allowed) { PluginSettings[plugin_id] = { - allowed: {} + allowed: {}, } } - let allowed = PluginSettings[plugin_id].allowed; + let allowed = PluginSettings[plugin_id].allowed if (module_name == 'fs' && options2.scope) { - if (typeof allowed[module_name] != 'object') allowed[module_name] = {directories: []} - allowed[module_name].directories.push(options2.scope); + if (typeof allowed[module_name] != 'object') + allowed[module_name] = { directories: [] } + allowed[module_name].directories.push(options2.scope) } else { - allowed[module_name] = true; + allowed[module_name] = true } - savePluginSettings(); + savePluginSettings() } if (result == Result.Uninstall) { setTimeout(() => { - plugin.uninstall(); - }, 20); + plugin.uninstall() + }, 20) } if (!(result == Result.Once || result == Result.Always)) { console.warn(`User denied access to "${module_name}" module`) - return; + return } - console.warn(`Gave plugin ${plugin_id} access to module ${module_name}`); + console.warn(`Gave plugin ${plugin_id} access to module ${module_name}`) } if (no_namespace_name == 'fs') { - return createScopedFS(options2.scope); + return createScopedFS(options2.scope) } else if (no_namespace_name == 'process') { - return process; + return process } else if (no_namespace_name == 'dialog') { - let api = {}; + let api = {} for (let key in dialog) { - api[key] = (options: any) => dialog[key](currentwindow, options); + api[key] = (options: any) => dialog[key](currentwindow, options) } - return api; + return api } - return require(module_name); + return require(module_name) } /** * @internal */ export function getPluginScopedRequire(plugin: InstanceType) { - const plugin_id = plugin.id; + const plugin_id = plugin.id return function require(module_id: string, options?: GetModuleOptions) { - return getModule(module_id, plugin_id, plugin, options); + return getModule(module_id, plugin_id, plugin, options) } } -const originalRequire = window.require; -delete window.require; +const originalRequire = window.require +delete window.require export function revokePluginPermissions(plugin: InstanceType): string[] { - let permissions = Object.keys(PluginSettings[plugin.id]?.allowed ?? {}); - delete PluginSettings[plugin.id]; - savePluginSettings(); - return permissions; + let permissions = Object.keys(PluginSettings[plugin.id]?.allowed ?? {}) + delete PluginSettings[plugin.id] + savePluginSettings() + return permissions } export function getPluginPermissions(plugin: InstanceType) { - let data = PluginSettings[plugin.id]?.allowed; - if (data) return parse(stringify(data)) as Record; + let data = PluginSettings[plugin.id]?.allowed + if (data) return parse(stringify(data)) as Record } export const SystemInfo = { @@ -207,7 +222,7 @@ export const SystemInfo = { * @internal */ export function getPCUsername() { - return process.env.USERNAME; + return process.env.USERNAME } /** * @internal @@ -223,10 +238,9 @@ export function openFileInEditor(file_path: string, editor: string) { Object.assign(window, { SystemInfo, Buffer, -}); +}) /** * TODO: * - Ensure it still works in the web app */ - diff --git a/js/native_apis_web.ts b/js/native_apis_web.ts index 5e62b084a..ec290bf8a 100644 --- a/js/native_apis_web.ts +++ b/js/native_apis_web.ts @@ -1,6 +1,6 @@ // Dummy exports for native APIs when running on web -const NULL = null; +const NULL = null /** @internal */ export { NULL as electron, @@ -30,7 +30,5 @@ export { * @internal */ export function getPCUsername() { - return ''; + return '' } - - diff --git a/js/outliner/armature.ts b/js/outliner/armature.ts index cee7eaf02..7b112e276 100644 --- a/js/outliner/armature.ts +++ b/js/outliner/armature.ts @@ -1,6 +1,6 @@ -import { Blockbench } from "../api"; -import { THREE, Vue } from "../lib/libs"; -import { ArmatureBone } from "./armature_bone"; +import { Blockbench } from '../api' +import { THREE, Vue } from '../lib/libs' +import { ArmatureBone } from './armature_bone' interface ArmatureOptions { name?: string @@ -18,21 +18,21 @@ export class Armature extends OutlinerElement { static preview_controller: NodePreviewController constructor(data?: ArmatureOptions, uuid?: UUID) { - super(data, uuid); + super(data, uuid) for (let key in Armature.properties) { - Armature.properties[key].reset(this); + Armature.properties[key].reset(this) } this.name = 'armature' - this.children = []; - this.selected = false; - this.locked = false; - this.export = true; - this.parent = 'root'; - this.isOpen = false; - this.visibility = true; - this.origin = [0, 0, 0]; + this.children = [] + this.selected = false + this.locked = false + this.export = true + this.parent = 'root' + this.isOpen = false + this.visibility = true + this.origin = [0, 0, 0] if (typeof data === 'object') { this.extend(data) @@ -45,35 +45,35 @@ export class Armature extends OutlinerElement { Armature.properties[key].merge(this, object) } Merge.string(this, object, 'name') - this.sanitizeName(); + this.sanitizeName() Merge.boolean(this, object, 'export') Merge.boolean(this, object, 'locked') Merge.boolean(this, object, 'visibility') - return this; + return this } getMesh() { - return this.mesh; + return this.mesh } init() { - super.init(); + super.init() if (!this.mesh || !this.mesh.parent) { // @ts-ignore - this.constructor.preview_controller.setup(this); + this.constructor.preview_controller.setup(this) } - return this; + return this } markAsSelected(descendants: boolean = false) { - Outliner.selected.safePush(this); - this.selected = true; + Outliner.selected.safePush(this) + this.selected = true if (descendants) { - this.children.forEach(child => child.markAsSelected(true)); + this.children.forEach(child => child.markAsSelected(true)) } - TickUpdates.selection = true; - return this; + TickUpdates.selection = true + return this } matchesSelection() { - let scope = this; - let match = true; + let scope = this + let match = true for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { return false @@ -84,7 +84,7 @@ export class Armature extends OutlinerElement { match = false } }) - return match; + return match } openUp() { this.isOpen = true @@ -92,7 +92,7 @@ export class Armature extends OutlinerElement { if (this.parent && this.parent !== 'root') { this.parent.openUp() } - return this; + return this } getSaveCopy() { let copy = { @@ -101,11 +101,11 @@ export class Armature extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - }; + } for (let key in Armature.properties) { - Armature.properties[key].merge(copy, this); + Armature.properties[key].merge(copy, this) } - return copy; + return copy } getUndoCopy() { let copy = { @@ -114,51 +114,60 @@ export class Armature extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - }; + } for (let key in Armature.properties) { - Armature.properties[key].merge(copy, this); + Armature.properties[key].merge(copy, this) } - return copy; + return copy } getChildlessCopy(keep_uuid?: boolean) { - let base_armature = new Armature({name: this.name}, keep_uuid ? this.uuid : null); + let base_armature = new Armature({ name: this.name }, keep_uuid ? this.uuid : null) for (let key in Armature.properties) { Armature.properties[key].copy(this, base_armature) } - base_armature.name = this.name; - base_armature.locked = this.locked; - base_armature.visibility = this.visibility; - base_armature.export = this.export; - base_armature.isOpen = this.isOpen; - return base_armature; + base_armature.name = this.name + base_armature.locked = this.locked + base_armature.visibility = this.visibility + base_armature.export = this.export + base_armature.isOpen = this.isOpen + return base_armature } - forEachChild(cb: ((element: OutlinerElement) => void), type?: typeof OutlinerNode, forSelf?: boolean) { + forEachChild( + cb: (element: OutlinerElement) => void, + type?: typeof OutlinerNode, + forSelf?: boolean + ) { let i = 0 if (forSelf) { cb(this) } while (i < this.children.length) { - if (!type || (type instanceof Array ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type)) { + if ( + !type || + (type instanceof Array + ? type.find(t2 => this.children[i] instanceof t2) + : this.children[i] instanceof type) + ) { // @ts-ignore cb(this.children[i]) } if (this.children[i].type === 'armature_bone') { this.children[i].forEachChild(cb, type) } - i++; + i++ } } getAllBones() { - let bones = []; + let bones = [] function addBones(array: ArmatureBone[]) { for (let item of array) { - if (item instanceof ArmatureBone == false) continue; - bones.push(item); - addBones(item.children); + if (item instanceof ArmatureBone == false) continue + bones.push(item) + addBones(item.children) } } - addBones(this.children); - return bones; + addBones(this.children) + return bones } static behavior = { unique_name: false, @@ -168,99 +177,102 @@ export class Armature extends OutlinerElement { child_types: ['armature_bone', 'mesh'], hide_in_screenshot: true, } - - public title = tl('data.armature'); - public type = 'armature'; - public icon = 'accessibility'; - public name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false; - public buttons = [ - Outliner.buttons.locked, - Outliner.buttons.visibility, - ]; + + public title = tl('data.armature') + public type = 'armature' + public icon = 'accessibility' + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false) + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility] public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, new MenuSeparator('settings'), new MenuSeparator('manage'), 'rename', - 'delete' - ]); - + 'delete', + ]) + static all: Armature[] static selected: Armature[] } -OutlinerElement.registerType(Armature, 'armature'); +OutlinerElement.registerType(Armature, 'armature') new NodePreviewController(Armature, { setup(element: Armature) { - let object_3d = new THREE.Object3D() as {isElement: boolean, no_export: boolean} & THREE.Object3D; - object_3d.rotation.order = 'ZYX'; - object_3d.uuid = element.uuid.toUpperCase(); - object_3d.name = element.name; - object_3d.isElement = true; - Project.nodes_3d[element.uuid] = object_3d; + let object_3d = new THREE.Object3D() as { + isElement: boolean + no_export: boolean + } & THREE.Object3D + object_3d.rotation.order = 'ZYX' + object_3d.uuid = element.uuid.toUpperCase() + object_3d.name = element.name + object_3d.isElement = true + Project.nodes_3d[element.uuid] = object_3d - object_3d.no_export = true; + object_3d.no_export = true - this.updateTransform(element); + this.updateTransform(element) - this.dispatchEvent('setup', {element}); + this.dispatchEvent('setup', { element }) }, updateTransform(element: Armature) { - let mesh = element.mesh; + let mesh = element.mesh - if (Format.bone_rig && element.parent instanceof OutlinerNode && element.parent.scene_object) { - element.parent.scene_object.add(mesh); + if ( + Format.bone_rig && + element.parent instanceof OutlinerNode && + element.parent.scene_object + ) { + element.parent.scene_object.add(mesh) } else if (mesh.parent !== Project.model_3d) { Project.model_3d.add(mesh) } - mesh.updateMatrixWorld(); + mesh.updateMatrixWorld() - this.dispatchEvent('update_transform', {element}); - } + this.dispatchEvent('update_transform', { element }) + }, }) - -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('add_armature', { icon: 'accessibility', category: 'edit', condition: () => Modes.edit && Project.format?.armature_rig, click: function () { - Undo.initEdit({outliner: true, elements: []}); - let add_to_node = Outliner.selected[0] || Group.first_selected; + Undo.initEdit({ outliner: true, elements: [] }) + let add_to_node = Outliner.selected[0] || Group.first_selected if (!add_to_node && selected.length) { - add_to_node = selected.last(); + add_to_node = selected.last() } - let armature = new Armature(); - armature.addTo(add_to_node); - armature.isOpen = true; - armature.createUniqueName(); - armature.init().select(); + let armature = new Armature() + armature.addTo(add_to_node) + armature.isOpen = true + armature.createUniqueName() + armature.init().select() if (add_to_node instanceof Mesh) { - add_to_node.addTo(armature); + add_to_node.addTo(armature) } - let bone = new ArmatureBone(); - bone.addTo(armature).init(); + let bone = new ArmatureBone() + bone.addTo(armature).init() // @ts-ignore - Undo.finishEdit('Add armature', {outliner: true, elements: [armature, bone]}); - Vue.nextTick(function() { + Undo.finishEdit('Add armature', { outliner: true, elements: [armature, bone] }) + Vue.nextTick(function () { updateSelection() if (settings.create_rename.value) { armature.rename() } armature.showInOutliner() - Blockbench.dispatchEvent( 'add_armature', {object: armature} ) + Blockbench.dispatchEvent('add_armature', { object: armature }) }) - } + }, }) }) Object.assign(window, { - Armature + Armature, }) diff --git a/js/outliner/armature_bone.ts b/js/outliner/armature_bone.ts index ba0832817..9e5348765 100644 --- a/js/outliner/armature_bone.ts +++ b/js/outliner/armature_bone.ts @@ -1,8 +1,8 @@ -import { Animation } from "../animations/animation"; -import { Blockbench } from "../api"; -import { THREE } from "../lib/libs"; -import { flipNameOnAxis } from "../modeling/transform"; -import { Armature } from "./armature"; +import { Animation } from '../animations/animation' +import { Blockbench } from '../api' +import { THREE } from '../lib/libs' +import { flipNameOnAxis } from '../modeling/transform' +import { Armature } from './armature' import { Vue } from '../lib/libs' interface ArmatureBoneOptions { @@ -19,7 +19,6 @@ interface ArmatureBoneOptions { color?: number } - export class ArmatureBone extends OutlinerElement { children: ArmatureBone[] isOpen: boolean @@ -32,27 +31,26 @@ export class ArmatureBone extends OutlinerElement { connected: boolean color: number old_size?: number - static preview_controller: NodePreviewController constructor(data?: ArmatureBoneOptions, uuid?: UUID) { - super(data, uuid); + super(data, uuid) for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].reset(this); + ArmatureBone.properties[key].reset(this) } this.name = 'bone' this.children = [] - this.selected = false; - this.locked = false; - this.export = true; - this.parent = 'root'; - this.isOpen = false; - this.visibility = true; - this.vertex_weights = {}; - this.color = Math.floor(Math.random()*markerColors.length); + this.selected = false + this.locked = false + this.export = true + this.parent = 'root' + this.isOpen = false + this.visibility = true + this.vertex_weights = {} + this.color = Math.floor(Math.random() * markerColors.length) if (typeof data === 'object') { this.extend(data) @@ -61,53 +59,53 @@ export class ArmatureBone extends OutlinerElement { } } get position() { - return this.origin; + return this.origin } extend(object: ArmatureBoneOptions) { for (let key in ArmatureBone.properties) { ArmatureBone.properties[key].merge(this, object) } Merge.string(this, object, 'name') - this.sanitizeName(); + this.sanitizeName() Merge.boolean(this, object, 'export') Merge.boolean(this, object, 'locked') Merge.boolean(this, object, 'visibility') - return this; + return this } getArmature(): Armature { - let parent = this.parent; + let parent = this.parent while (parent instanceof Armature == false && parent instanceof OutlinerNode) { - parent = parent.parent; + parent = parent.parent } - return parent as Armature; + return parent as Armature } init(): this { - super.init(); + super.init() if (!this.mesh || !this.mesh.parent) { - this.preview_controller.setup(this); + this.preview_controller.setup(this) } - Canvas.updateAllBones([this]); - return this; + Canvas.updateAllBones([this]) + return this } select(event?: Event, isOutlinerClick?: boolean): this { - super.select(event, isOutlinerClick); + super.select(event, isOutlinerClick) if (Animator.open && Animation.selected) { - Animation.selected.getBoneAnimator(this).select(true); + Animation.selected.getBoneAnimator(this).select(true) } - return this; + return this } markAsSelected(descendants: boolean): this { - Outliner.selected.safePush(this); - this.selected = true; + Outliner.selected.safePush(this) + this.selected = true if (descendants) { - this.children.forEach(child => child.markAsSelected(true)); + this.children.forEach(child => child.markAsSelected(true)) } - TickUpdates.selection = true; - return this; + TickUpdates.selection = true + return this } matchesSelection() { - let scope = this; - let match = true; + let scope = this + let match = true for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { return false @@ -118,98 +116,102 @@ export class ArmatureBone extends OutlinerElement { match = false } }) - return match; + return match } openUp() { - this.isOpen = true; - this.updateElement(); + this.isOpen = true + this.updateElement() if (this.parent && this.parent !== 'root') { - this.parent.openUp(); + this.parent.openUp() } - return this; + return this } transferOrigin(origin: ArrayVector3) { - if (!this.mesh) return; + if (!this.mesh) return let q = new THREE.Quaternion().copy(this.mesh.quaternion) let shift = new THREE.Vector3( this.origin[0] - origin[0], this.origin[1] - origin[1], - this.origin[2] - origin[2], + this.origin[2] - origin[2] ) let dq = new THREE.Vector3().copy(shift) dq.applyQuaternion(q) shift.sub(dq) shift.applyQuaternion(q.invert()) - this.origin.V3_set(origin); + this.origin.V3_set(origin) function iterateChild(obj: ArmatureBone) { if (obj instanceof ArmatureBone) { - obj.origin.V3_add(shift); - obj.children.forEach(child => iterateChild(child)); + obj.origin.V3_add(shift) + obj.children.forEach(child => iterateChild(child)) } } - this.children.forEach(child => iterateChild(child)); + this.children.forEach(child => iterateChild(child)) Canvas.updatePositions() - return this; + return this } getWorldCenter(): THREE.Vector3 { - let pos = new THREE.Vector3(); - this.mesh.localToWorld(pos); - return pos; + let pos = new THREE.Vector3() + this.mesh.localToWorld(pos) + return pos } flip(axis: number, center: number): this { var offset = this.position[axis] - center - this.position[axis] = center - offset; + this.position[axis] = center - offset this.rotation.forEach((n, i) => { - if (i != axis) this.rotation[i] = -n; + if (i != axis) this.rotation[i] = -n }) // Name - flipNameOnAxis(this, axis); + flipNameOnAxis(this, axis) - this.createUniqueName(); - this.preview_controller.updateTransform(this); - return this; + this.createUniqueName() + this.preview_controller.updateTransform(this) + return this } size(): ArrayVector3 size(axis: axisLetter): number size(axis?: axisLetter): number | ArrayVector3 { if (typeof axis == 'number') { - return axis == 1 ? this.length : this.width; + return axis == 1 ? this.length : this.width } - return [this.width, this.length, this.width]; + return [this.width, this.length, this.width] } getSize(axis) { - return this.size(axis); + return this.size(axis) } - resize(move_value: number | ((input: number) => number), axis_number?: axisNumber, invert?: boolean) { + resize( + move_value: number | ((input: number) => number), + axis_number?: axisNumber, + invert?: boolean + ) { if (axis_number == 1) { - let previous_length = this.old_size ?? this.length; + let previous_length = this.old_size ?? this.length if (typeof move_value == 'function') { - this.length = move_value(previous_length); + this.length = move_value(previous_length) } else { - this.length = previous_length + move_value * (invert ? -1 : 1); + this.length = previous_length + move_value * (invert ? -1 : 1) } } else { - let previous_width = this.old_size ?? this.width; + let previous_width = this.old_size ?? this.width if (typeof move_value == 'function') { - this.width = move_value(previous_width); + this.width = move_value(previous_width) } else { - this.width = previous_width + move_value * (invert ? -1 : 1); + this.width = previous_width + move_value * (invert ? -1 : 1) } } - this.preview_controller.updateTransform(this); + this.preview_controller.updateTransform(this) } setColor(index) { - this.color = index; - this.preview_controller.updateFaces(this); - let armature = this.getArmature(); + this.color = index + this.preview_controller.updateFaces(this) + let armature = this.getArmature() // Update vertex colors Canvas.updateView({ elements: Mesh.all.filter(mesh => armature && mesh.getArmature() == armature), - element_aspects: {geometry: true} - }); - return this; + element_aspects: { geometry: true }, + }) + return this } getSaveCopy(project) { let copy = { @@ -218,11 +220,11 @@ export class ArmatureBone extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - }; + } for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(copy, this); + ArmatureBone.properties[key].merge(copy, this) } - return copy; + return copy } getUndoCopy() { let copy = { @@ -231,39 +233,44 @@ export class ArmatureBone extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - }; + } for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(copy, this); + ArmatureBone.properties[key].merge(copy, this) } - return copy; + return copy } getChildlessCopy(keep_uuid: boolean = false) { - let base_bone = new ArmatureBone({name: this.name}, keep_uuid ? this.uuid : null); + let base_bone = new ArmatureBone({ name: this.name }, keep_uuid ? this.uuid : null) for (let key in ArmatureBone.properties) { ArmatureBone.properties[key].copy(this, base_bone) } - base_bone.name = this.name; - base_bone.origin.V3_set(this.origin); - base_bone.rotation.V3_set(this.rotation); - base_bone.locked = this.locked; - base_bone.visibility = this.visibility; - base_bone.export = this.export; - base_bone.isOpen = this.isOpen; - return base_bone; + base_bone.name = this.name + base_bone.origin.V3_set(this.origin) + base_bone.rotation.V3_set(this.rotation) + base_bone.locked = this.locked + base_bone.visibility = this.visibility + base_bone.export = this.export + base_bone.isOpen = this.isOpen + return base_bone } - forEachChild(cb: ((element: ArmatureBone) => void), type?: any, forSelf?: boolean) { + forEachChild(cb: (element: ArmatureBone) => void, type?: any, forSelf?: boolean) { let i = 0 if (forSelf) { cb(this) } while (i < this.children.length) { - if (!type || (type instanceof Array ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type)) { + if ( + !type || + (type instanceof Array + ? type.find(t2 => this.children[i] instanceof t2) + : this.children[i] instanceof type) + ) { cb(this.children[i]) } if (this.children[i].type === 'armature_bone') { this.children[i].forEachChild(cb, type) } - i++; + i++ } } static behavior = { @@ -280,61 +287,67 @@ export class ArmatureBone extends OutlinerElement { } static all: ArmatureBone[] static selected: ArmatureBone[] - - public title = tl('data.armature_bone'); - public type = 'armature_bone'; - public icon = 'humerus'; - public name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false; - public buttons = [ - Outliner.buttons.locked, - Outliner.buttons.visibility, - ]; + + public title = tl('data.armature_bone') + public type = 'armature_bone' + public icon = 'humerus' + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false) + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility] public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, new MenuSeparator('settings'), 'set_element_marker_color', - "randomize_marker_colors", + 'randomize_marker_colors', 'apply_animation_preset', new MenuSeparator('manage'), 'rename', - 'delete' - ]); + 'delete', + ]) } ArmatureBone.addBehaviorOverride({ - condition: {features: ['bone_rig']}, + condition: { features: ['bone_rig'] }, behavior: { - unique_name: true - } + unique_name: true, + }, }) -OutlinerElement.registerType(ArmatureBone, 'armature_bone'); +OutlinerElement.registerType(ArmatureBone, 'armature_bone') -new Property(ArmatureBone, 'vector', 'origin', {default: [0, 0, 0]}); -new Property(ArmatureBone, 'vector', 'rotation'); -new Property(ArmatureBone, 'number', 'length', {default: 8}); -new Property(ArmatureBone, 'number', 'width', {default: 2}); +new Property(ArmatureBone, 'vector', 'origin', { default: [0, 0, 0] }) +new Property(ArmatureBone, 'vector', 'rotation') +new Property(ArmatureBone, 'number', 'length', { default: 8 }) +new Property(ArmatureBone, 'number', 'width', { default: 2 }) new Property(ArmatureBone, 'boolean', 'connected', { default: true, inputs: { element_panel: { - input: {label: 'armature_bone.connected', type: 'checkbox'}, + input: { label: 'armature_bone.connected', type: 'checkbox' }, onChange() { - let parents = []; + let parents = [] ArmatureBone.selected.forEach(b => { if (b.parent instanceof ArmatureBone) parents.safePush(b.parent) - }); + }) console.log(parents) - Canvas.updateView({elements: parents, element_aspects: {transform: true}}); - } - } - } -}); -new Property(ArmatureBone, 'number', 'color'); -new Property(ArmatureBone, 'object', 'vertex_weights'); + Canvas.updateView({ elements: parents, element_aspects: { transform: true } }) + }, + }, + }, +}) +new Property(ArmatureBone, 'number', 'color') +new Property(ArmatureBone, 'object', 'vertex_weights') -type FakeObjectType = {isElement: boolean, no_export: boolean, fix_position: THREE.Vector3, fix_rotation: THREE.Euler, inverse_bind_matrix: THREE.Matrix4}; -type PreviewControllerType = (NodePreviewController & {material: THREE.MeshLambertMaterial, material_selected: THREE.MeshLambertMaterial}); +type FakeObjectType = { + isElement: boolean + no_export: boolean + fix_position: THREE.Vector3 + fix_rotation: THREE.Euler + inverse_bind_matrix: THREE.Matrix4 +} +type PreviewControllerType = NodePreviewController & { + material: THREE.MeshLambertMaterial + material_selected: THREE.MeshLambertMaterial +} new NodePreviewController(ArmatureBone, { material: new THREE.MeshLambertMaterial({ color: 0xc8c9cb, @@ -353,141 +366,203 @@ new NodePreviewController(ArmatureBone, { side: THREE.FrontSide, }), setup(element: ArmatureBone) { - let object_3d = new THREE.Bone() as FakeObjectType & THREE.Bone; - object_3d.rotation.order = 'ZYX'; - object_3d.uuid = element.uuid.toUpperCase(); - object_3d.name = element.name; + let object_3d = new THREE.Bone() as FakeObjectType & THREE.Bone + object_3d.rotation.order = 'ZYX' + object_3d.uuid = element.uuid.toUpperCase() + object_3d.name = element.name //object_3d.isElement = true; - Project.nodes_3d[element.uuid] = object_3d; + Project.nodes_3d[element.uuid] = object_3d - let geometry = new THREE.BufferGeometry(); - let r = 1, m = 0.2; + let geometry = new THREE.BufferGeometry() + let r = 1, + m = 0.2 let vertices = [ - 0,0,0,r,m,r,-r,m,r, - r,m,-r, 0,0,0, -r,m,-r, - r,m,r, 0,0,0, r,m,-r, - 0,0,0,-r,m,r,-r,m,-r, - r,m,r, 0,1,0, -r,m,r, - 0,1,0, r,m,-r, -r,m,-r, - 0,1,0, r,m,r, r,m,-r, - -r,m,r, 0,1,0, -r,m,-r, - ]; + 0, + 0, + 0, + r, + m, + r, + -r, + m, + r, + r, + m, + -r, + 0, + 0, + 0, + -r, + m, + -r, + r, + m, + r, + 0, + 0, + 0, + r, + m, + -r, + 0, + 0, + 0, + -r, + m, + r, + -r, + m, + -r, + r, + m, + r, + 0, + 1, + 0, + -r, + m, + r, + 0, + 1, + 0, + r, + m, + -r, + -r, + m, + -r, + 0, + 1, + 0, + r, + m, + r, + r, + m, + -r, + -r, + m, + r, + 0, + 1, + 0, + -r, + m, + -r, + ] let normals = [ - 0.0,-0.5,0.4, - 0.0,-0.5,-0.4, - 0.4,-0.5,0.0, - -0.4,-0.5,0.0, - 0.0,0.2,0.6, - 0.0,0.2,-0.6, - 0.6,0.2,0.0, - -0.6,0.2,0.0, - ]; - let normals_array = []; + 0.0, -0.5, 0.4, 0.0, -0.5, -0.4, 0.4, -0.5, 0.0, -0.4, -0.5, 0.0, 0.0, 0.2, 0.6, 0.0, + 0.2, -0.6, 0.6, 0.2, 0.0, -0.6, 0.2, 0.0, + ] + let normals_array = [] for (let i = 0; i < normals.length; i += 3) { for (let j = 0; j < 3; j++) { - normals_array.push(normals[i], normals[i+1], normals[i+2]); + normals_array.push(normals[i], normals[i + 1], normals[i + 2]) } } - geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); - geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals_array, 3)); - geometry.normalizeNormals(); - geometry.computeBoundingBox(); - geometry.computeBoundingSphere(); - let material = (ArmatureBone.preview_controller as PreviewControllerType).material; - let mesh: ({no_export?: boolean, isElement?: true, type?: string} & THREE.Mesh) = new THREE.Mesh(geometry, material); - mesh.renderOrder = 20; - mesh.visible = element.visibility; - mesh.no_export = true; - mesh.name = element.uuid; - mesh.type = element.type; - mesh.isElement = true; - object_3d.add(mesh); + geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)) + geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals_array, 3)) + geometry.normalizeNormals() + geometry.computeBoundingBox() + geometry.computeBoundingSphere() + let material = (ArmatureBone.preview_controller as PreviewControllerType).material + let mesh: { no_export?: boolean; isElement?: true; type?: string } & THREE.Mesh = + new THREE.Mesh(geometry, material) + mesh.renderOrder = 20 + mesh.visible = element.visibility + mesh.no_export = true + mesh.name = element.uuid + mesh.type = element.type + mesh.isElement = true + object_3d.add(mesh) + object_3d.no_export = true + object_3d.fix_position = new THREE.Vector3() + object_3d.fix_rotation = new THREE.Euler() + object_3d.inverse_bind_matrix = new THREE.Matrix4() - object_3d.no_export = true; - object_3d.fix_position = new THREE.Vector3(); - object_3d.fix_rotation = new THREE.Euler(); - object_3d.inverse_bind_matrix = new THREE.Matrix4(); + this.updateTransform(element) + this.updateFaces(element) + this.updateSelection(element) - this.updateTransform(element); - this.updateFaces(element); - this.updateSelection(element); - - this.dispatchEvent('setup', {element}); + this.dispatchEvent('setup', { element }) }, updateFaces(element: ArmatureBone) { - let color_material = Canvas.coloredSolidMaterials[element.color % markerColors.length]; - let color_value = color_material.uniforms.base.value; - let color_array = []; + let color_material = Canvas.coloredSolidMaterials[element.color % markerColors.length] + let color_value = color_material.uniforms.base.value + let color_array = [] for (let i = 0; i < 24; i++) { - color_array.push(color_value.r, color_value.g, color_value.b); + color_array.push(color_value.r, color_value.g, color_value.b) } - (element.mesh.children[0] as THREE.Mesh).geometry.setAttribute('color', new THREE.Float32BufferAttribute(color_array, 3)); + ;(element.mesh.children[0] as THREE.Mesh).geometry.setAttribute( + 'color', + new THREE.Float32BufferAttribute(color_array, 3) + ) }, updateTransform(element: ArmatureBone) { - let bone = element.scene_object as FakeObjectType & THREE.Bone; - let armature = element.getArmature(); + let bone = element.scene_object as FakeObjectType & THREE.Bone - bone.rotation.order = 'ZYX'; + bone.rotation.order = 'ZYX' // @ts-expect-error - bone.rotation.setFromDegreeArray(element.rotation); - bone.position.fromArray(element.origin); - bone.scale.x = bone.scale.y = bone.scale.z = 1; + bone.rotation.setFromDegreeArray(element.rotation) + bone.position.fromArray(element.origin) + bone.scale.x = bone.scale.y = bone.scale.z = 1 if (element.parent instanceof OutlinerNode) { - let parent_bone = element.parent.scene_object; - parent_bone.add(bone); + let parent_bone = element.parent.scene_object + parent_bone.add(bone) if (element.parent instanceof ArmatureBone) { - ArmatureBone.preview_controller.updateTransform(element.parent); + ArmatureBone.preview_controller.updateTransform(element.parent) } } else if (bone.parent) { - bone.parent.remove(bone); + bone.parent.remove(bone) } - let connected_children = element.children.filter(b => b.connected); + let connected_children = element.children.filter(b => b.connected) if (connected_children.length >= 2) { - let box = new THREE.Box3(); + let box = new THREE.Box3() for (let bone of connected_children) { - box.expandByPoint(Reusable.vec1.fromArray(bone.position)); + box.expandByPoint(Reusable.vec1.fromArray(bone.position)) } - let tail_offset = box.getCenter(Reusable.vec1); - bone.children[0].scale.y = Math.max(2, tail_offset.length()); - bone.children[0].quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), tail_offset.normalize()); - + let tail_offset = box.getCenter(Reusable.vec1) + bone.children[0].scale.y = Math.max(2, tail_offset.length()) + bone.children[0].quaternion.setFromUnitVectors( + new THREE.Vector3(0, 1, 0), + tail_offset.normalize() + ) } else if (connected_children.length == 1) { - let tail_offset = Reusable.vec1.fromArray(connected_children[0].position); - bone.children[0].scale.y = tail_offset.length(); - bone.children[0].quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), tail_offset.normalize()); - + let tail_offset = Reusable.vec1.fromArray(connected_children[0].position) + bone.children[0].scale.y = tail_offset.length() + bone.children[0].quaternion.setFromUnitVectors( + new THREE.Vector3(0, 1, 0), + tail_offset.normalize() + ) } else { - bone.children[0].rotation.set(0,0,0); - bone.children[0].scale.x = element.width/2; - bone.children[0].scale.z = element.width/2; - bone.children[0].scale.y = element.length; + bone.children[0].rotation.set(0, 0, 0) + bone.children[0].scale.x = element.width / 2 + bone.children[0].scale.z = element.width / 2 + bone.children[0].scale.y = element.length } - bone.fix_position.copy(bone.position); - bone.fix_rotation.copy(bone.rotation); - - bone.updateMatrixWorld(); - if (armature?.scene_object) { - bone.inverse_bind_matrix.copy(armature.scene_object.matrixWorld).invert(); - bone.inverse_bind_matrix.multiply(bone.matrixWorld); - bone.inverse_bind_matrix.invert(); - } else { - bone.inverse_bind_matrix.copy(bone.matrixWorld).invert(); - } + bone.fix_position.copy(bone.position) + bone.fix_rotation.copy(bone.rotation) + bone.inverse_bind_matrix.copy(bone.matrixWorld).invert() + + /*for (let child of element.children) { + if (child.scene_object) this.updateTransform(child); + }*/ - this.dispatchEvent('update_transform', {element}); + bone.updateMatrixWorld() + + this.dispatchEvent('update_transform', { element }) }, updateSelection(element: ArmatureBone) { - let material = element.selected ? this.material_selected : this.material; - let preview_mesh = element.scene_object.children[0] as THREE.Mesh; - preview_mesh.material = material; - } + let material = element.selected ? this.material_selected : this.material + let preview_mesh = element.scene_object.children[0] as THREE.Mesh + preview_mesh.material = material + }, }) - export function getAllArmatureBones() { let ta = [] function iterate(array) { @@ -499,45 +574,48 @@ export function getAllArmatureBones() { } } iterate(Outliner.root) - return ta; + return ta } -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('add_armature_bone', { icon: 'humerus', category: 'edit', - keybind: new Keybind({key: 'e', shift: true}), + keybind: new Keybind({ key: 'e', shift: true }), condition: () => Modes.edit && (ArmatureBone.selected[0] || Armature.selected[0]), click: function () { - Undo.initEdit({outliner: true, elements: []}); - let add_to_node = Outliner.selected[0] || Group.first_selected; + Undo.initEdit({ outliner: true, elements: [] }) + let add_to_node = Outliner.selected[0] || Group.first_selected if (!add_to_node && selected.length) { - add_to_node = selected.last(); + add_to_node = selected.last() } let new_instance = new ArmatureBone({ - origin: add_to_node instanceof ArmatureBone ? [0, add_to_node.length??8, 0] : undefined, + origin: + add_to_node instanceof ArmatureBone + ? [0, add_to_node.length ?? 8, 0] + : undefined, }) new_instance.addTo(add_to_node) new_instance.isOpen = true - + if (Format.bone_rig) { new_instance.createUniqueName() } new_instance.init().select() - Undo.finishEdit('Add armature bone', {outliner: true, elements: [new_instance]}); - Vue.nextTick(function() { + Undo.finishEdit('Add armature bone', { outliner: true, elements: [new_instance] }) + Vue.nextTick(function () { updateSelection() if (settings.create_rename.value) { new_instance.rename() } new_instance.showInOutliner() - Blockbench.dispatchEvent( 'add_armature_bone', {object: new_instance} ) + Blockbench.dispatchEvent('add_armature_bone', { object: new_instance }) }) - } + }, }) }) Object.assign(window, { ArmatureBone, - getAllArmatureBones + getAllArmatureBones, }) diff --git a/js/outliner/collections.ts b/js/outliner/collections.ts index 7fc6b5df1..0188a280c 100644 --- a/js/outliner/collections.ts +++ b/js/outliner/collections.ts @@ -1,21 +1,21 @@ -import { Animation } from "../animations/animation"; -import { SharedActions } from "../interface/shared_actions"; -import { Prop } from "../misc"; -import { guid } from "../util/math_util"; -import { Property } from "../util/property"; -import { OutlinerElement, OutlinerNode } from "./outliner"; +import { Animation } from '../animations/animation' +import { SharedActions } from '../interface/shared_actions' +import { Prop } from '../misc' +import { guid } from '../util/math_util' +import { Property } from '../util/property' +import { OutlinerElement, OutlinerNode } from './outliner' import { Toolbar } from '../interface/toolbars' -import { Group } from "./group"; -import { Interface } from "../interface/interface"; -import { Menu } from "../interface/menu"; -import { Blockbench } from "../api"; -import { removeEventListeners } from "../util/util"; +import { Group } from './group' +import { Interface } from '../interface/interface' +import { Menu } from '../interface/menu' +import { Blockbench } from '../api' +import { removeEventListeners } from '../util/util' import { Action } from '../interface/actions' -import { Clipbench } from "../copy_paste"; -import { getFocusedTextInput } from "../interface/keyboard"; -import { tl } from "../languages"; -import { Panel } from "../interface/panels"; -import { Codecs } from "../io/codec"; +import { Clipbench } from '../copy_paste' +import { getFocusedTextInput } from '../interface/keyboard' +import { tl } from '../languages' +import { Panel } from '../interface/panels' +import { Codecs } from '../io/codec' interface CollectionOptions { children?: string[] @@ -53,69 +53,73 @@ export class Collection { static selected: Collection[] constructor(data: CollectionOptions, uuid?: string) { - this.uuid = (uuid && isUUID(uuid)) ? uuid : guid(); - this.selected = false; - this.children = []; + this.uuid = uuid && isUUID(uuid) ? uuid : guid() + this.selected = false + this.children = [] for (let key in Collection.properties) { - Collection.properties[key].reset(this); + Collection.properties[key].reset(this) } - if (data) this.extend(data); + if (data) this.extend(data) } extend(data: CollectionOptions): this { for (var key in Collection.properties) { Collection.properties[key].merge(this, data) } - return this; + return this } select(event?: KeyboardEvent | MouseEvent): this { - this.selected = true; - if ((!(event?.shiftKey || Pressing.overrides.shift) && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) || Modes.animate) { - unselectAllElements(); - Collection.all.forEach(c => c.selected = false); + this.selected = true + if ( + (!(event?.shiftKey || Pressing.overrides.shift) && + !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) || + Modes.animate + ) { + unselectAllElements() + Collection.all.forEach(c => (c.selected = false)) } - this.selected = true; - let i = 0; + this.selected = true + let i = 0 if (Modes.animate && Animation.selected && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) { - Timeline.animators.empty(); + Timeline.animators.empty() } for (let node of this.getChildren()) { if (Modes.animate && Animation.selected) { // @ts-ignore if (node.constructor.animator) { - let animator = Animation.selected.getBoneAnimator(node); + let animator = Animation.selected.getBoneAnimator(node) if (animator) { - animator.addToTimeline(true); + animator.addToTimeline(true) } if (i == 0) { - node.select(); + node.select() } } } else { if (node instanceof Group) { - node.multiSelect(); + node.multiSelect() } else { - Outliner.selected.safePush(node); + Outliner.selected.safePush(node) } } - i++; + i++ } - updateSelection(); - return this; + updateSelection() + return this } clickSelect(event) { - Undo.initSelection({collections: true, timeline: Modes.animate}); - this.select(event); - Undo.finishSelection('Select collection'); + Undo.initSelection({ collections: true, timeline: Modes.animate }) + this.select(event) + Undo.finishSelection('Select collection') } /** * Get all direct children */ getChildren(): OutlinerNode[] { - return this.children.map(uuid => OutlinerNode.uuids[uuid]).filter(node => node != undefined); + return this.children.map(uuid => OutlinerNode.uuids[uuid]).filter(node => node != undefined) } add(): this { - Collection.all.safePush(this); - return this; + Collection.all.safePush(this) + return this } /** * Adds the current outliner selection to this collection @@ -123,103 +127,103 @@ export class Collection { addSelection(): this { if (Group.multi_selected.length) { for (let group of Group.multi_selected) { - this.children.safePush(group.uuid); + this.children.safePush(group.uuid) } } for (let element of Outliner.selected) { if (!(element instanceof OutlinerNode && element.parent.selected)) { - this.children.safePush(element.uuid); + this.children.safePush(element.uuid) } } - return this; + return this } /** * Returns the visibility of the first contained node that supports visibility. Otherwise returns true. */ getVisibility(): boolean { let match = this.getChildren().find(node => { - return node && 'visibility' in node && typeof node.visibility == 'boolean'; - }); + return node && 'visibility' in node && typeof node.visibility == 'boolean' + }) // @ts-ignore - return match ? match.visibility : true; + return match ? match.visibility : true } /** * Get all children, including indirect ones */ getAllChildren(): OutlinerNode[] { - let children = this.getChildren(); - let nodes = []; + let children = this.getChildren() + let nodes = [] for (let child of children) { - nodes.safePush(child); + nodes.safePush(child) if ('forEachChild' in child && typeof child.forEachChild == 'function') { - child.forEachChild(subchild => nodes.safePush(subchild)); + child.forEachChild(subchild => nodes.safePush(subchild)) } } - return nodes; + return nodes } /** * Toggle visibility of everything in the collection * @param event If the alt key is pressed, the result is inverted and the visibility of everything but the collection will be toggled */ toggleVisibility(event: KeyboardEvent | MouseEvent): void { - let children = this.getChildren(); - if (!children.length) return; - let groups = []; - let elements = []; + let children = this.getChildren() + if (!children.length) return + let groups = [] + let elements = [] function update(node: OutlinerNode) { - if ('visibility' in node == false || typeof node.visibility != 'boolean') return; + if ('visibility' in node == false || typeof node.visibility != 'boolean') return if (node instanceof Group) { - groups.push(node); + groups.push(node) } else { - elements.push(node); + elements.push(node) } } for (let child of children) { - update(child); + update(child) if ('forEachChild' in child && typeof child.forEachChild == 'function') { - child.forEachChild(update); + child.forEachChild(update) } } if (event.altKey) { // invert selection - elements = Outliner.elements.filter(e => !elements.includes(e)); - groups = Group.all.filter(e => !groups.includes(e)); + elements = Outliner.elements.filter(e => !elements.includes(e)) + groups = Group.all.filter(e => !groups.includes(e)) } - let all = groups.concat(elements); - let state = all[0]?.visibility != true; - Undo.initEdit({groups, elements}); + let all = groups.concat(elements) + let state = all[0]?.visibility != true + Undo.initEdit({ groups, elements }) all.forEach(node => { - node.visibility = state; + node.visibility = state }) - Canvas.updateView({elements, element_aspects: {visibility: true}}); - Undo.finishEdit('Toggle collection visibility'); + Canvas.updateView({ elements, element_aspects: { visibility: true } }) + Undo.finishEdit('Toggle collection visibility') } /** * Opens the context menu */ showContextMenu(event) { - if (!this.selected) this.clickSelect(event); - this.menu.open(event, this); - return this; + if (!this.selected) this.clickSelect(event) + this.menu.open(event, this) + return this } getUndoCopy() { let copy = { uuid: this.uuid, - index: Collection.all.indexOf(this) - }; + index: Collection.all.indexOf(this), + } for (var key in Collection.properties) { - Collection.properties[key].copy(this, copy); + Collection.properties[key].copy(this, copy) } - return copy; + return copy } getSaveCopy() { let copy = { - uuid: this.uuid - }; + uuid: this.uuid, + } for (var key in Collection.properties) { - Collection.properties[key].copy(this, copy); + Collection.properties[key].copy(this, copy) } - return copy; + return copy } /** * Opens the properties dialog @@ -232,28 +236,31 @@ export class Collection { * Export Format * Offset */ - let collection = this; + let collection = this function getContentList() { let types = { - group: [] + group: [], } for (let child of collection.getChildren()) { // @ts-ignore - let type = child.type; - if (!types[type]) types[type] = []; - types[type].push(child); + let type = child.type + if (!types[type]) types[type] = [] + types[type].push(child) } - let list = []; + let list = [] for (let key in types) { for (let node of types[key]) { list.push({ name: node.name, uuid: node.uuid, - icon: key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon + icon: + key == 'group' + ? Group.prototype.icon + : OutlinerElement.types[key].prototype.icon, }) } } - return list; + return list } type PropertiesComponentData = { content: { @@ -269,15 +276,15 @@ export class Collection { resizable: 'x', keyboard_actions: { delete: { - keybind: new Keybind({key: 46}), + keybind: new Keybind({ key: 46 }), run() { - this.content_vue.remove(); - } - } + this.content_vue.remove() + }, + }, }, part_order: ['form', 'component'], form: { - name: {type: 'text', label: 'generic.name', value: this.name}, + name: { type: 'text', label: 'generic.name', value: this.name }, export_path: { label: 'dialog.collection.export_path', value: this.export_path, @@ -285,46 +292,47 @@ export class Collection { condition: isApp && this.codec, extensions: ['json'], filetype: 'JSON collection', - } + }, }, component: { - components: {VuePrismEditor}, + components: { VuePrismEditor }, data: { content: getContentList(), - selected: [] + selected: [], } as PropertiesComponentData, methods: { selectAll(this: PropertiesComponentData) { for (let node of this.content) { - this.selected.safePush(node.uuid); + this.selected.safePush(node.uuid) } }, selectNone(this: PropertiesComponentData) { - this.selected.empty(); + this.selected.empty() }, remove(this: PropertiesComponentData) { for (let uuid of this.selected) { - this.content.remove(this.content.find(node => node.uuid == uuid)); + this.content.remove(this.content.find(node => node.uuid == uuid)) } - this.selected.empty(); + this.selected.empty() }, addWithFilter(this: PropertiesComponentData, event) { // @ts-ignore - BarItems.select_window.click(event, {returnResult: ({elements, groups}) => { - for (let node of elements.concat(groups)) { - if (!this.content.find(node2 => node2.uuid == node.uuid)) { - this.content.push({ - uuid: node.uuid, - name: node.name, - icon: node.icon - }) + BarItems.select_window.click(event, { + returnResult: ({ elements, groups }) => { + for (let node of elements.concat(groups)) { + if (!this.content.find(node2 => node2.uuid == node.uuid)) { + this.content.push({ + uuid: node.uuid, + name: node.name, + icon: node.icon, + }) + } } - } - }}) + }, + }) }, }, - template: - `
    + template: `
    • @@ -337,39 +345,41 @@ export class Collection {
    -
    ` +
    `, }, onFormChange(form) { - this.component.data.loop_mode = form.loop; + this.component.data.loop_mode = form.loop }, onConfirm: form_data => { - let vue_data = dialog.content_vue.$data as PropertiesComponentData; + let vue_data = dialog.content_vue.$data as PropertiesComponentData if ( form_data.name != this.name || form_data.export_path != this.export_path || vue_data.content.find(node => !collection.children.includes(node.uuid)) || - collection.children.find(uuid => !vue_data.content.find(node => node.uuid == uuid)) + collection.children.find( + uuid => !vue_data.content.find(node => node.uuid == uuid) + ) ) { - Undo.initEdit({collections: [this]}); + Undo.initEdit({ collections: [this] }) this.extend({ name: form_data.name, export_path: form_data.export_path, }) - if (isApp) this.export_path = form_data.path; - this.children.replace(vue_data.content.map(node => node.uuid)); + if (isApp) this.export_path = form_data.path + this.children.replace(vue_data.content.map(node => node.uuid)) - Blockbench.dispatchEvent('edit_collection_properties', {collection: this}) + Blockbench.dispatchEvent('edit_collection_properties', { collection: this }) - Undo.finishEdit('Edit collection properties'); + Undo.finishEdit('Edit collection properties') } - dialog.hide().delete(); + dialog.hide().delete() }, onCancel() { - dialog.hide().delete(); - } + dialog.hide().delete() + }, }) - dialog.show(); + dialog.show() } } Collection.prototype.menu = new Menu([ @@ -382,18 +392,22 @@ Collection.prototype.menu = new Menu([ 'duplicate', 'delete', new MenuSeparator('export'), - (collection) => { - let codec = Codecs[collection.codec]; - if (codec?.export_action && collection.export_path && Condition(codec.export_action.condition)) { - let export_action = codec.export_action; + collection => { + let codec = Codecs[collection.codec] + if ( + codec?.export_action && + collection.export_path && + Condition(codec.export_action.condition) + ) { + let export_action = codec.export_action return { id: 'export_as', name: tl('menu.collection.export_as', pathToName(collection.export_path, true)), icon: export_action.icon, description: export_action.description, click() { - codec.writeCollection(collection); - } + codec.writeCollection(collection) + }, } } }, @@ -401,20 +415,25 @@ Collection.prototype.menu = new Menu([ id: 'export', name: 'generic.export', icon: 'insert_drive_file', - children: (collection) => { - let actions = []; + children: collection => { + let actions = [] for (let id in Codecs) { - let codec = Codecs[id]; - if (!codec.export_action || !codec.support_partial_export || !Condition(codec.export_action.condition)) continue; + let codec = Codecs[id] + if ( + !codec.export_action || + !codec.support_partial_export || + !Condition(codec.export_action.condition) + ) + continue - let export_action = codec.export_action; + let export_action = codec.export_action let new_action = { name: export_action.name, icon: export_action.icon, description: export_action.description, click() { - codec.exportCollection(collection); - } + codec.exportCollection(collection) + }, } if (id == 'project') { new_action = { @@ -422,157 +441,162 @@ Collection.prototype.menu = new Menu([ icon: 'icon-blockbench_file', description: '', click() { - codec.exportCollection(collection); - } + codec.exportCollection(collection) + }, } } - actions.push(new_action); + actions.push(new_action) } - return actions; - } + return actions + }, }, new MenuSeparator('properties'), { icon: 'list', name: 'menu.texture.properties', - click(collection) { collection.propertiesDialog()} - } + click(collection) { + collection.propertiesDialog() + }, + }, ]) -new Property(Collection, 'string', 'name', {default: 'collection'}); -new Property(Collection, 'string', 'export_codec'); -new Property(Collection, 'string', 'export_path'); -new Property(Collection, 'array', 'children'); -new Property(Collection, 'boolean', 'visibility', {default: false}); +new Property(Collection, 'string', 'name', { default: 'collection' }) +new Property(Collection, 'string', 'export_codec') +new Property(Collection, 'string', 'export_path') +new Property(Collection, 'array', 'children') +new Property(Collection, 'boolean', 'visibility', { default: false }) Object.defineProperty(Collection, 'all', { get() { // @ts-ignore return Project.collections - } + }, }) Object.defineProperty(Collection, 'selected', { get() { // @ts-ignore - return Project ? Project.collections.filter(c => c.selected) : []; - } + return Project ? Project.collections.filter(c => c.selected) : [] + }, }) SharedActions.add('delete', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - let selected = Collection.selected.slice(); - Undo.initEdit({collections: selected}); + let selected = Collection.selected.slice() + Undo.initEdit({ collections: selected }) for (let c of selected) { Collection.all.remove(c) } - selected.empty(); - Undo.finishEdit('Remove collection'); - } + selected.empty() + Undo.finishEdit('Remove collection') + }, }) SharedActions.add('duplicate', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - let new_collections = []; - Undo.initEdit({collections: new_collections}); + let new_collections = [] + Undo.initEdit({ collections: new_collections }) for (let original of Collection.selected.slice()) { - let copy = new Collection(original); - copy.name += ' - copy'; - copy.add().select(); - new_collections.push(copy); + let copy = new Collection(original) + copy.name += ' - copy' + copy.add().select() + new_collections.push(copy) } - Undo.finishEdit('Duplicate collection'); - } + Undo.finishEdit('Duplicate collection') + }, }) SharedActions.add('copy', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - Clipbench.collections = Collection.selected.map(collection => collection.getUndoCopy()); - } + Clipbench.collections = Collection.selected.map(collection => collection.getUndoCopy()) + }, }) SharedActions.add('paste', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Clipbench.collections?.length, run() { - let new_collections = []; - Undo.initEdit({collections: new_collections}); + let new_collections = [] + Undo.initEdit({ collections: new_collections }) for (let data of Clipbench.collections) { - let copy = new Collection(data); - copy.name += ' - copy'; - copy.add().select(); - new_collections.push(copy); + let copy = new Collection(data) + copy.name += ' - copy' + copy.add().select() + new_collections.push(copy) } - Undo.finishEdit('Paste collection'); - } + Undo.finishEdit('Paste collection') + }, }) BARS.defineActions(() => { new Action('create_collection', { icon: 'inventory_2', category: 'select', - keybind: new Keybind({key: 'l', ctrl: true}), - condition: {modes: ['edit', 'paint', 'animate']}, + keybind: new Keybind({ key: 'l', ctrl: true }), + condition: { modes: ['edit', 'paint', 'animate'] }, click() { - Undo.initEdit({collections: []}); - let collection = new Collection({}); - collection.add().addSelection().select(); - Undo.finishEdit('Create collection', {collections: [collection]}); - updateSelection(); - } + Undo.initEdit({ collections: [] }) + let collection = new Collection({}) + collection.add().addSelection().select() + Undo.finishEdit('Create collection', { collections: [collection] }) + updateSelection() + }, }) new Action('set_collection_content_to_selection', { icon: 'unarchive', category: 'select', condition: () => Collection.selected.length, click() { - let collections = Collection.selected; - Undo.initEdit({collections}); + let collections = Collection.selected + Undo.initEdit({ collections }) for (let collection of collections) { - collection.children.empty(); - collection.addSelection(); + collection.children.empty() + collection.addSelection() } - Undo.finishEdit('Set collection content to selection'); - } + Undo.finishEdit('Set collection content to selection') + }, }) new Action('add_to_collection', { icon: 'box_add', category: 'select', condition: () => Collection.selected.length, click() { - let collections = Collection.selected; - Undo.initEdit({collections}); + let collections = Collection.selected + Undo.initEdit({ collections }) for (let collection of collections) { - collection.addSelection(); + collection.addSelection() } - Undo.finishEdit('Add selection to collection'); - } + Undo.finishEdit('Add selection to collection') + }, }) }) -Interface.definePanels(function() { - +Interface.definePanels(function () { function eventTargetToCollection(target: HTMLElement): [Collection?, HTMLElement?] { - let target_node: HTMLElement | undefined = target; - let i = 0; - while (target_node && target_node.classList && !target_node.classList.contains('collection')) { + let target_node: HTMLElement | undefined = target + let i = 0 + while ( + target_node && + target_node.classList && + !target_node.classList.contains('collection') + ) { if (i < 3 && target_node) { - target_node = target_node.parentElement; - i++; + target_node = target_node.parentElement + i++ } else { - return []; + return [] } } - let uuid_value = target_node.getAttribute('uuid') as string; - return [Collection.all.find(collection => collection.uuid == uuid_value), target_node]; + let uuid_value = target_node.getAttribute('uuid') as string + return [Collection.all.find(collection => collection.uuid == uuid_value), target_node] } function getOrder(loc, obj) { if (!obj) { - return; + return } else { - if (loc <= 20) return -1; - return 1; + if (loc <= 20) return -1 + return 1 } } new Panel('collections', { @@ -585,158 +609,166 @@ Interface.definePanels(function() { float_position: [0, 0], float_size: [300, 300], height: 300, - folded: false + folded: false, }, - condition: {modes: ['edit', 'paint', 'animate'], method: () => (!Format.image_editor)}, + condition: { modes: ['edit', 'paint', 'animate'], method: () => !Format.image_editor }, toolbars: [ new Toolbar('collections', { - children: [ - 'create_collection', - ] - }) + children: ['create_collection'], + }), ], component: { name: 'panel-collections', - data() { return { - collections: [], - }}, + data() { + return { + collections: [], + } + }, methods: { openMenu(event) { Interface.Panels.collections.menu.show(event) }, dragCollection(e1) { - if (getFocusedTextInput()) return; - if (e1.button == 1 || e1.button == 2) return; - convertTouchEvent(e1); + if (getFocusedTextInput()) return + if (e1.button == 1 || e1.button == 2) return + convertTouchEvent(e1) - let [collection] = eventTargetToCollection(e1.target); - if (!collection) return; - let active = false; - let helper: HTMLDivElement; - let timeout: NodeJS.Timeout | null = null; - let drop_target, drop_target_node, order; - let last_event = e1; + let [collection] = eventTargetToCollection(e1.target) + if (!collection) return + let active = false + let helper: HTMLDivElement + let timeout: NodeJS.Timeout | null = null + let drop_target, drop_target_node, order + let last_event = e1 function move(e2) { - convertTouchEvent(e2); - let offset = [ - e2.clientX - e1.clientX, - e2.clientY - e1.clientY, - ] + convertTouchEvent(e2) + let offset = [e2.clientX - e1.clientX, e2.clientY - e1.clientY] if (!active) { - let distance = Math.sqrt(Math.pow(offset[0], 2) + Math.pow(offset[1], 2)) + let distance = Math.sqrt( + Math.pow(offset[0], 2) + Math.pow(offset[1], 2) + ) if (Blockbench.isTouch) { if (distance > 20 && timeout) { - clearTimeout(timeout); - timeout = null; + clearTimeout(timeout) + timeout = null } else { - document.getElementById('collections_list').scrollTop += last_event.clientY - e2.clientY; + document.getElementById('collections_list').scrollTop += + last_event.clientY - e2.clientY } } else if (distance > 6) { - active = true; + active = true } } else { - if (e2) e2.preventDefault(); - - if (Menu.open) Menu.open.hide(); + if (e2) e2.preventDefault() + + if (Menu.open) Menu.open.hide() if (!helper) { - helper = document.createElement('div'); - helper.id = 'animation_drag_helper'; - let icon = Blockbench.getIconNode('inventory_2'); helper.append(icon); - let span = document.createElement('span'); span.innerText = collection.name; helper.append(span); - document.body.append(helper); - Blockbench.addFlag('dragging_collections'); + helper = document.createElement('div') + helper.id = 'animation_drag_helper' + let icon = Blockbench.getIconNode('inventory_2') + helper.append(icon) + let span = document.createElement('span') + span.innerText = collection.name + helper.append(span) + document.body.append(helper) + Blockbench.addFlag('dragging_collections') } - helper.style.left = `${e2.clientX}px`; - helper.style.top = `${e2.clientY}px`; + helper.style.left = `${e2.clientX}px` + helper.style.top = `${e2.clientY}px` // drag - $('.drag_hover').removeClass('drag_hover'); - $('.collection[order]').attr('order', null); + $('.drag_hover').removeClass('drag_hover') + $('.collection[order]').attr('order', null) - let target = document.elementFromPoint(e2.clientX, e2.clientY); - [drop_target, drop_target_node] = eventTargetToCollection(target as HTMLElement); + let target = document.elementFromPoint(e2.clientX, e2.clientY) + ;[drop_target, drop_target_node] = eventTargetToCollection( + target as HTMLElement + ) if (drop_target) { - var location = e2.clientY - $(drop_target_node).offset().top; + var location = e2.clientY - $(drop_target_node).offset().top order = getOrder(location, drop_target) drop_target_node.setAttribute('order', order) - drop_target_node.classList.add('drag_hover'); + drop_target_node.classList.add('drag_hover') } } - last_event = e2; + last_event = e2 } function off(e2) { - if (helper) helper.remove(); - removeEventListeners(document, 'mousemove touchmove', move); - removeEventListeners(document, 'mouseup touchend', off); - $('.drag_hover').removeClass('drag_hover'); - $('.collection[order]').attr('order', null); - if (Blockbench.isTouch) clearTimeout(timeout); - + if (helper) helper.remove() + removeEventListeners(document, 'mousemove touchmove', move) + removeEventListeners(document, 'mouseup touchend', off) + $('.drag_hover').removeClass('drag_hover') + $('.collection[order]').attr('order', null) + if (Blockbench.isTouch) clearTimeout(timeout) + setTimeout(() => { - Blockbench.removeFlag('dragging_collections'); - }, 10); + Blockbench.removeFlag('dragging_collections') + }, 10) if (active && !Menu.open) { - convertTouchEvent(e2); - let target = document.elementFromPoint(e2.clientX, e2.clientY); - let [target_collection] = eventTargetToCollection(target as HTMLElement); - if (!target_collection || target_collection == collection ) return; + convertTouchEvent(e2) + let target = document.elementFromPoint(e2.clientX, e2.clientY) + let [target_collection] = eventTargetToCollection(target as HTMLElement) + if (!target_collection || target_collection == collection) return + + let index = Collection.all.indexOf(target_collection) + if (index == -1) return + if (Collection.all.indexOf(collection) < index) index-- + if (order == 1) index++ + if (Collection.all[index] == collection) return - let index = Collection.all.indexOf(target_collection); - if (index == -1) return; - if (Collection.all.indexOf(collection) < index) index--; - if (order == 1) index++; - if (Collection.all[index] == collection) return; - - Undo.initEdit({collections: [collection]}); + Undo.initEdit({ collections: [collection] }) - Collection.all.remove(collection); - Collection.all.splice(index, 0, collection); + Collection.all.remove(collection) + Collection.all.splice(index, 0, collection) - Undo.finishEdit('Reorder collections'); + Undo.finishEdit('Reorder collections') } } if (Blockbench.isTouch) { timeout = setTimeout(() => { - active = true; - move(e1); + active = true + move(e1) }, 320) } - addEventListeners(document, 'mousemove touchmove', move, {passive: false}); - addEventListeners(document, 'mouseup touchend', off, {passive: false}); + addEventListeners(document, 'mousemove touchmove', move, { passive: false }) + addEventListeners(document, 'mouseup touchend', off, { passive: false }) }, unselect() { - if (Blockbench.hasFlag('dragging_collections')) return; + if (Blockbench.hasFlag('dragging_collections')) return Collection.all.forEach(collection => { - collection.selected = false; + collection.selected = false }) - updateSelection(); + updateSelection() }, getContentList(collection: Collection) { let types = { - group: [] + group: [], } for (let child of collection.getChildren()) { // @ts-ignore - let type = child.type; - if (!types[type]) types[type] = []; - types[type].push(child); + let type = child.type + if (!types[type]) types[type] = [] + types[type].push(child) } - let list = []; + let list = [] for (let key in types) { - if (!types[key].length) continue; + if (!types[key].length) continue list.push({ count: types[key].length == 1 ? '' : types[key].length, name: types[key].length == 1 ? types[key][0].name : '', - icon: key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon + icon: + key == 'group' + ? Group.prototype.icon + : OutlinerElement.types[key].prototype.icon, }) } - return list; - } + return list + }, }, template: `
    - ` + `, }, - menu: new Menu([ - 'create_collection', - 'copy', - ]) + menu: new Menu(['create_collection', 'copy']), }) }) Object.assign(window, { - Collection -}); + Collection, +}) diff --git a/js/outliner/element_panel.ts b/js/outliner/element_panel.ts index 3b501cf17..9d5a66ce2 100644 --- a/js/outliner/element_panel.ts +++ b/js/outliner/element_panel.ts @@ -1,19 +1,19 @@ -import { Blockbench } from "../api"; -import { InputForm } from "../interface/form"; -import { Interface } from "../interface/interface"; -import { Panel } from "../interface/panels"; -import { Property } from "../util/property"; +import { Blockbench } from '../api' +import { InputForm } from '../interface/form' +import { Interface } from '../interface/interface' +import { Panel } from '../interface/panels' +import { Property } from '../util/property' -Interface.definePanels(function() { +Interface.definePanels(function () { new Panel('transform', { icon: 'arrows_output', - condition: {modes: ['edit', 'pose']}, + condition: { modes: ['edit', 'pose'] }, display_condition: () => Outliner.selected.length || Group.first_selected, default_position: { slot: 'right_bar', float_position: [0, 0], float_size: [300, 400], - height: 400 + height: 400, }, toolbars: [ Toolbars.element_position, @@ -25,7 +25,7 @@ Interface.definePanels(function() { }) let element_properties_panel = new Panel('element', { icon: 'fas.fa-cube', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, display_condition: () => Outliner.selected.length || Group.first_selected, default_position: { slot: 'right_bar', @@ -33,93 +33,93 @@ Interface.definePanels(function() { float_size: [300, 400], height: 400, attached_to: 'transform', - attached_index: 1 + attached_index: 1, }, - form: new InputForm({}) + form: new InputForm({}), }) function updateElementForm() { - const {form_config} = element_properties_panel.form; + const { form_config } = element_properties_panel.form for (let key in form_config) { - delete form_config[key]; + delete form_config[key] } - let onchanges = []; + let onchanges = [] let registerInput = (type_id: string, prop_id: string, property: Property) => { - if (!property?.inputs?.element_panel) return; - let {input, onChange} = property.inputs.element_panel; - let input_id = type_id + '_' + prop_id; + if (!property?.inputs?.element_panel) return + let { input, onChange } = property.inputs.element_panel + let input_id = type_id + '_' + prop_id input.condition = { - selected: {[type_id]: true}, + selected: { [type_id]: true }, method: () => Condition(property.condition), - }; - if (onChange) onchanges.push(onChange); - form_config[input_id] = input; + } + if (onChange) onchanges.push(onChange) + form_config[input_id] = input } for (let type_id in OutlinerElement.types) { - let type = OutlinerElement.types[type_id]; + let type = OutlinerElement.types[type_id] for (let prop_id in type.properties) { - registerInput(type_id, prop_id, type.properties[prop_id]); + registerInput(type_id, prop_id, type.properties[prop_id]) } } for (let prop_id in Group.properties) { - let property = Group.properties[prop_id]; + let property = Group.properties[prop_id] if (property?.inputs?.element_panel) { - registerInput('group', prop_id, Group.properties[prop_id]); + registerInput('group', prop_id, Group.properties[prop_id]) } } - element_properties_panel.form.on('input', ({result, changed_keys}) => { + element_properties_panel.form.on('input', ({ result, changed_keys }) => { // Only one key should be changed at a time if (changed_keys[0]?.startsWith('group_')) { - let groups = Group.multi_selected; - Undo.initEdit({groups}); + let groups = Group.multi_selected + Undo.initEdit({ groups }) for (let group of groups) { for (let key in result) { - let property_id = key.replace(group.type+'_', ''); + let property_id = key.replace(group.type + '_', '') // @ts-ignore if (group.constructor.properties[property_id]) { - group[property_id] = result[key]; + group[property_id] = result[key] } } } - Undo.finishEdit('Change group property'); - onchanges.forEach(onchange => onchange(result)); + Undo.finishEdit('Change group property') + onchanges.forEach(onchange => onchange(result)) } else { - let elements = Outliner.selected.slice(); - Undo.initEdit({elements}); + let elements = Outliner.selected.slice() + Undo.initEdit({ elements }) for (let element of elements) { for (let key in result) { - let property_id = key.replace(element.type+'_', ''); + let property_id = key.replace(element.type + '_', '') // @ts-ignore if (element.constructor.properties[property_id]) { - element[property_id] = result[key]; + element[property_id] = result[key] } } } - Undo.finishEdit('Change element property'); - onchanges.forEach(onchange => onchange(result)); + Undo.finishEdit('Change element property') + onchanges.forEach(onchange => onchange(result)) } }) - element_properties_panel.form.buildForm(); + element_properties_panel.form.buildForm() } - updateElementForm(); + updateElementForm() Blockbench.on('register_element_type', () => { - updateElementForm(); - }); + updateElementForm() + }) Blockbench.on('update_selection', () => { - let values = {}; + let values = {} for (let type_id in OutlinerElement.types) { - let type = OutlinerElement.types[type_id]; - let first_element = type.selected[0]; + let type = OutlinerElement.types[type_id] + let first_element = type.selected[0] if (first_element) { for (let prop_id in type.properties) { - let property = type.properties[prop_id]; + let property = type.properties[prop_id] if (property?.inputs?.element_panel) { - let input_id = type_id + '_' + prop_id; - if (typeof first_element[prop_id] === "object") { // Prevent object properties from using the same objects across elements. - values[input_id] = {...first_element[prop_id]}; - } - else { - values[input_id] = first_element[prop_id]; + let input_id = type_id + '_' + prop_id + if (typeof first_element[prop_id] === 'object') { + // Prevent object properties from using the same objects across elements. + values[input_id] = { ...first_element[prop_id] } + } else { + values[input_id] = first_element[prop_id] } } } @@ -127,16 +127,18 @@ Interface.definePanels(function() { } if (Group.multi_selected.length) { for (let prop_id in Group.properties) { - let property = Group.properties[prop_id]; + let property = Group.properties[prop_id] if (property?.inputs?.element_panel) { - let input_id = 'group_' + prop_id; - values[input_id] = Group.first_selected[prop_id]; + let input_id = 'group_' + prop_id + values[input_id] = Group.first_selected[prop_id] } } } - element_properties_panel.form.setValues(values); - element_properties_panel.form.update(values); - element_properties_panel.form.updateLabelWidth(true); - }); - Toolbars.element_origin.node.after(Interface.createElement('div', {id: 'element_origin_toolbar_anchor'})) -}) \ No newline at end of file + element_properties_panel.form.setValues(values) + element_properties_panel.form.update(values) + element_properties_panel.form.updateLabelWidth(true) + }) + Toolbars.element_origin.node.after( + Interface.createElement('div', { id: 'element_origin_toolbar_anchor' }) + ) +}) diff --git a/js/plugin_loader.ts b/js/plugin_loader.ts index 6881fd95e..5cbc838b7 100644 --- a/js/plugin_loader.ts +++ b/js/plugin_loader.ts @@ -1,14 +1,21 @@ -import { Blockbench } from "./api"; -import StateMemory from "./util/state_memory"; -import { Dialog } from "./interface/dialog"; -import { settings, Settings, SettingsProfile } from "./interface/settings"; -import { ModelLoader, StartScreen } from "./interface/start_screen"; -import { sort_collator } from "./misc"; -import { separateThousands } from "./util/math_util"; -import { getDateDisplay } from "./util/util"; -import { Filesystem } from "./file_system"; -import { Panels } from "./interface/interface"; -import { app, fs, getPluginPermissions, getPluginScopedRequire, https, revokePluginPermissions } from "./native_apis"; +import { Blockbench } from './api' +import StateMemory from './util/state_memory' +import { Dialog } from './interface/dialog' +import { settings, Settings, SettingsProfile } from './interface/settings' +import { ModelLoader, StartScreen } from './interface/start_screen' +import { sort_collator } from './misc' +import { separateThousands } from './util/math_util' +import { getDateDisplay } from './util/util' +import { Filesystem } from './file_system' +import { Panels } from './interface/interface' +import { + app, + fs, + getPluginPermissions, + getPluginScopedRequire, + https, + revokePluginPermissions, +} from './native_apis' interface FileResult { name: string @@ -20,7 +27,7 @@ export const Plugins = { /** * The plugins window */ - dialog: null as null|Dialog, + dialog: null as null | Dialog, Vue: null as Vue | null, /** * Data about which plugins are installed @@ -38,60 +45,64 @@ export const Plugins = { /** * The currently used path to the plugin API */ - api_path: settings.cdn_mirror.value ? 'https://blckbn.ch/cdn/plugins' : 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins', + api_path: settings.cdn_mirror.value + ? 'https://blckbn.ch/cdn/plugins' + : 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins', path: '', /** * Dev reload all side-loaded plugins */ devReload() { - let reloads = 0; - for (let i = Plugins.all.length-1; i >= 0; i--) { - let plugin = Plugins.all[i]; + let reloads = 0 + for (let i = Plugins.all.length - 1; i >= 0; i--) { + let plugin = Plugins.all[i] if (plugin.source == 'file' && plugin.isReloadable()) { Plugins.all[i].reload() - reloads++; + reloads++ } } Blockbench.showQuickMessage(tl('message.plugin_reload', [reloads])) - console.log('Reloaded '+reloads+ ' plugin'+pluralS(reloads)) + console.log('Reloaded ' + reloads + ' plugin' + pluralS(reloads)) }, /** * Update sort order of existing plugins */ sort() { Plugins.all.sort((a, b) => { - if (a.tags.find(tag => tag.match(/deprecated/i))) return 1; - if (b.tags.find(tag => tag.match(/deprecated/i))) return -1; - let download_difference = (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0); + if (a.tags.find(tag => tag.match(/deprecated/i))) return 1 + if (b.tags.find(tag => tag.match(/deprecated/i))) return -1 + let download_difference = + (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0) if (download_difference) { return download_difference } else { - return sort_collator.compare(a.title, b.title); + return sort_collator.compare(a.title, b.title) } - }); - } + }) + }, } StateMemory.init('installed_plugins', 'array') // @ts-ignore -Plugins.installed = StateMemory.installed_plugins = StateMemory.installed_plugins.filter(p => p && typeof p == 'object'); - +Plugins.installed = StateMemory.installed_plugins = StateMemory.installed_plugins.filter( + p => p && typeof p == 'object' +) -type PluginVariant = 'desktop'|'web'|'both'; -type PluginSource = 'store'|'file'|'url'; +type PluginVariant = 'desktop' | 'web' | 'both' +type PluginSource = 'store' | 'file' | 'url' type PluginDetails = { - version: string, - last_modified: string, - creation_date: string, - last_modified_full: string, - creation_date_full: string, + version: string + last_modified: string + creation_date: string + last_modified_full: string + creation_date_full: string min_version: string max_version: string website: string repository: string bug_tracker: string contributors: string - author: string, - variant: PluginVariant | string, + author: string + variant: PluginVariant | string permissions: string weekly_installations: string } @@ -103,15 +114,18 @@ type PluginInstallation = { dependencies?: string[] disabled?: boolean } -type PluginChangelog = Record + author?: string + date?: string + categories: { + title: string + list: string[] + }[] + } +> interface PluginOptions { title: string @@ -223,18 +237,18 @@ export class Plugin { repository: string bug_tracker: string source: PluginSource - creation_date: string|number + creation_date: string | number contributes: {} await_loading: boolean has_changelog: boolean - changelog: null|PluginChangelog + changelog: null | PluginChangelog about_fetched: boolean changelog_fetched: boolean disabled: boolean new_repository_format: boolean cache_version: number menu: Menu - details: null|PluginDetails + details: null | PluginDetails onload?: () => void onunload?: () => void @@ -242,41 +256,41 @@ export class Plugin { onuninstall?: () => void constructor(id: string = 'unknown', data?: PluginOptions | PluginSetupOptions) { - this.id = id; - this.installed = false; - this.title = ''; - this.author = ''; - this.description = ''; - this.about = ''; - this.icon = ''; - this.tags = []; - this.dependencies = []; - this.contributors = []; - this.version = '0.0.1'; - this.variant = 'both'; - this.min_version = ''; - this.max_version = ''; - this.deprecation_note = ''; - this.website = ''; - this.source = 'store'; - this.creation_date = 0; - this.contributes = {}; - this.await_loading = false; - this.has_changelog = false; - this.changelog = null; - this.details = null; - this.about_fetched = false; - this.changelog_fetched = false; - this.disabled = false; - this.new_repository_format = false; - this.cache_version = 0; + this.id = id + this.installed = false + this.title = '' + this.author = '' + this.description = '' + this.about = '' + this.icon = '' + this.tags = [] + this.dependencies = [] + this.contributors = [] + this.version = '0.0.1' + this.variant = 'both' + this.min_version = '' + this.max_version = '' + this.deprecation_note = '' + this.website = '' + this.source = 'store' + this.creation_date = 0 + this.contributes = {} + this.await_loading = false + this.has_changelog = false + this.changelog = null + this.details = null + this.about_fetched = false + this.changelog_fetched = false + this.disabled = false + this.new_repository_format = false + this.cache_version = 0 this.extend(data) - Plugins.all.safePush(this); + Plugins.all.safePush(this) } extend(data) { - if (!(data instanceof Object)) return this; + if (!(data instanceof Object)) return this Merge.boolean(this, data, 'installed') Merge.string(this, data, 'title') Merge.string(this, data, 'author') @@ -291,246 +305,274 @@ export class Plugin { Merge.string(this, data, 'website') Merge.string(this, data, 'repository') Merge.string(this, data, 'bug_tracker') - Merge.boolean(this, data, 'await_loading'); - Merge.boolean(this, data, 'has_changelog'); - Merge.boolean(this, data, 'disabled'); - if (data.creation_date) this.creation_date = Date.parse(data.creation_date); - if (data.tags instanceof Array) this.tags.safePush(...data.tags.slice(0, 3)); - if (data.contributors instanceof Array) this.contributors.safePush(...data.contributors); - if (data.dependencies instanceof Array) this.dependencies.safePush(...data.dependencies); - - if (data.new_repository_format) this.new_repository_format = true; + Merge.boolean(this, data, 'await_loading') + Merge.boolean(this, data, 'has_changelog') + Merge.boolean(this, data, 'disabled') + if (data.creation_date) this.creation_date = Date.parse(data.creation_date) + if (data.tags instanceof Array) this.tags.safePush(...data.tags.slice(0, 3)) + if (data.contributors instanceof Array) this.contributors.safePush(...data.contributors) + if (data.dependencies instanceof Array) this.dependencies.safePush(...data.dependencies) + + if (data.new_repository_format) this.new_repository_format = true if (this.min_version != '' && !compareVersions('4.8.0', this.min_version)) { - this.new_repository_format = true; + this.new_repository_format = true } if (typeof data.contributes == 'object') { - this.contributes = data.contributes; + this.contributes = data.contributes } Merge.function(this, data, 'onload') Merge.function(this, data, 'onunload') Merge.function(this, data, 'oninstall') Merge.function(this, data, 'onuninstall') - return this; + return this } get name() { - return this.title; + return this.title } async install() { if (this.tags.includes('Deprecated') || this.deprecation_note) { - let message = tl('message.plugin_deprecated.message'); + let message = tl('message.plugin_deprecated.message') if (this.deprecation_note) { - message += '\n\n*' + this.deprecation_note + '*'; + message += '\n\n*' + this.deprecation_note + '*' } - let answer = await new Promise((resolve) => { - Blockbench.showMessageBox({ - icon: 'warning', - title: this.title, - message, - cancelIndex: 0, - buttons: ['dialog.cancel', 'message.plugin_deprecated.install_anyway'] - }, resolve) + let answer = await new Promise(resolve => { + Blockbench.showMessageBox( + { + icon: 'warning', + title: this.title, + message, + cancelIndex: 0, + buttons: ['dialog.cancel', 'message.plugin_deprecated.install_anyway'], + }, + resolve + ) }) - if (answer == 0) return; + if (answer == 0) return } - return await this.download(true); + return await this.download(true) } async load(first: boolean = false, cb?: (this: Plugin) => void) { - var scope = this; - Plugins.registered[this.id] = this; + var scope = this + Plugins.registered[this.id] = this return await new Promise((resolve, reject) => { - let path = Plugins.path + scope.id + '.js'; - if (!isApp && this.new_repository_format) { - path = `${Plugins.path}${scope.id}/${scope.id}.js`; + let path = Plugins.path + scope.id + '.js' + if (!isApp && this.new_repository_format) { + path = `${Plugins.path}${scope.id}/${scope.id}.js` } - this.#runPluginFile(path).then((content) => { - if (cb) cb.bind(scope)() - if (first && scope.oninstall) { - scope.oninstall() - } - if (first) Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])); - resolve() - }).catch((error) => { - if (isApp) { - console.log('Could not find file of plugin "'+scope.id+'". Uninstalling it instead.') - scope.uninstall() - } - if (first) Blockbench.showQuickMessage(tl('message.installed_plugin_fail', [this.title])); - reject() - console.error(error) - }) + this.#runPluginFile(path) + .then(content => { + if (cb) cb.bind(scope)() + if (first && scope.oninstall) { + scope.oninstall() + } + if (first) + Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])) + resolve() + }) + .catch(error => { + if (isApp) { + console.log( + 'Could not find file of plugin "' + + scope.id + + '". Uninstalling it instead.' + ) + scope.uninstall() + } + if (first) + Blockbench.showQuickMessage( + tl('message.installed_plugin_fail', [this.title]) + ) + reject() + console.error(error) + }) this.#remember() - scope.installed = true; + scope.installed = true }) } async installDependencies(first) { - let required_dependencies = []; + let required_dependencies = [] for (let id of this.dependencies) { - let saved_install = !first && Plugins.installed.find(p => p.id == id); + let saved_install = !first && Plugins.installed.find(p => p.id == id) if (saved_install) { - continue; + continue } - let plugin = Plugins.all.find(p => p.id == id); + let plugin = Plugins.all.find(p => p.id == id) if (plugin) { - if (plugin.installed == false) required_dependencies.push(plugin); - continue; + if (plugin.installed == false) required_dependencies.push(plugin) + continue } - required_dependencies.push(id); + required_dependencies.push(id) } if (required_dependencies.length) { let failed_dependency = required_dependencies.find(p => { return !p.isInstallable || p.isInstallable() != true - }); + }) if (failed_dependency) { - let error_message = failed_dependency; + let error_message = failed_dependency if (failed_dependency instanceof Plugin) { - error_message = `**${failed_dependency.title}**: ${failed_dependency.isInstallable()}`; + error_message = `**${failed_dependency.title}**: ${failed_dependency.isInstallable()}` } Blockbench.showMessageBox({ title: 'message.plugin_dependencies.title', - message: `Updating **${this.title||this.id}**:\n\n${tl('message.plugin_dependencies.invalid')}\n\n${error_message}`, - }); - return false; + message: `Updating **${this.title || this.id}**:\n\n${tl('message.plugin_dependencies.invalid')}\n\n${error_message}`, + }) + return false } - let list = required_dependencies.map(p => `**${p.title}** ${tl('dialog.plugins.author', [p.author])}`); + let list = required_dependencies.map( + p => `**${p.title}** ${tl('dialog.plugins.author', [p.author])}` + ) let response = await new Promise(resolve => { - Blockbench.showMessageBox({ - title: 'message.plugin_dependencies.title', - message: `${tl('message.plugin_dependencies.' + (first ? 'message1' : 'message1_update'), [this.title])} \n\n* ${ list.join('\n* ') }\n\n${tl('message.plugin_dependencies.message2')}`, - buttons: ['dialog.continue', first ? 'dialog.cancel' : 'dialog.plugins.uninstall'], - width: 512, - }, button => { - resolve(button == 0); - }) + Blockbench.showMessageBox( + { + title: 'message.plugin_dependencies.title', + message: `${tl('message.plugin_dependencies.' + (first ? 'message1' : 'message1_update'), [this.title])} \n\n* ${list.join('\n* ')}\n\n${tl('message.plugin_dependencies.message2')}`, + buttons: [ + 'dialog.continue', + first ? 'dialog.cancel' : 'dialog.plugins.uninstall', + ], + width: 512, + }, + button => { + resolve(button == 0) + } + ) }) if (!response) { - if (this.installed) this.uninstall(); - return false; + if (this.installed) this.uninstall() + return false } for (let dependency of required_dependencies) { - await dependency.install(); + await dependency.install() } } - return true; + return true } async download(first = false) { - let response = await this.installDependencies(first); - if (response == false) return; + let response = await this.installDependencies(first) + if (response == false) return - var scope = this; + var scope = this function register() { - if (!Plugins.json[scope.id]) return; + if (!Plugins.json[scope.id]) return jQuery.ajax({ url: 'https://blckbn.ch/api/event/install_plugin', type: 'POST', data: { - plugin: scope.id - } + plugin: scope.id, + }, }) } if (!isApp) { - if (first) register(); + if (first) register() return await scope.load(first) } // Download files - async function copyFileToDrive(origin_filename?: string, target_filename?: string, callback?: () => void) { - var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename)); + async function copyFileToDrive( + origin_filename?: string, + target_filename?: string, + callback?: () => void + ) { + var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename)) // @ts-ignore - https.get(Plugins.api_path+'/'+origin_filename, function(response) { - response.pipe(file); - if (callback) response.on('end', callback); - }); + https.get(Plugins.api_path + '/' + origin_filename, function (response) { + response.pipe(file) + if (callback) response.on('end', callback) + }) } return await new Promise(async (resolve, reject) => { // New system if (this.new_repository_format) { copyFileToDrive(`${this.id}/${this.id}.js`, `${this.id}.js`, () => { - if (first) register(); - setTimeout(async function() { - await scope.load(first); + if (first) register() + setTimeout(async function () { + await scope.load(first) resolve() }, 20) - }); + }) if (this.hasImageIcon()) { - copyFileToDrive(`${this.id}/${this.icon}`, this.id + '.' + this.icon); + copyFileToDrive(`${this.id}/${this.icon}`, this.id + '.' + this.icon) } - await this.fetchAbout(); + await this.fetchAbout() if (this.about) { - fs.writeFileSync(PathModule.join(Plugins.path, this.id + '.about.md'), this.about, 'utf-8'); + fs.writeFileSync( + PathModule.join(Plugins.path, this.id + '.about.md'), + this.about, + 'utf-8' + ) } - } else { // Legacy system copyFileToDrive(`${this.id}.js`, `${this.id}.js`, () => { - if (first) register(); - setTimeout(async function() { - await scope.load(first); + if (first) register() + setTimeout(async function () { + await scope.load(first) resolve() }, 20) - }); + }) } - }); + }) } async loadFromFile(file: Filesystem.FileResult, first = false) { - var scope = this; - if (!isApp && !first) return this; + var scope = this + if (!isApp && !first) return this if (first) { if (isApp) { - if (!confirm(tl('message.load_plugin_app'))) return; + if (!confirm(tl('message.load_plugin_app'))) return } else { - if (!confirm(tl('message.load_plugin_web'))) return; + if (!confirm(tl('message.load_plugin_web'))) return } } - this.id = pathToName(file.path); - Plugins.registered[this.id] = this; - Plugins.all.safePush(this); - this.source = 'file'; - this.tags.safePush('Local'); + this.id = pathToName(file.path) + Plugins.registered[this.id] = this + Plugins.all.safePush(this) + this.source = 'file' + this.tags.safePush('Local') if (isApp) { - let content = await this.#runPluginFile(file.path).catch((error) => { - console.error(error); - }); + let content = await this.#runPluginFile(file.path).catch(error => { + console.error(error) + }) if (content) { if (first && scope.oninstall) { scope.oninstall() } - scope.path = file.path; + scope.path = file.path } } else { - this.#runCode(file.content as string); + this.#runCode(file.content as string) if (first && scope.oninstall) { scope.oninstall() } } - this.installed = true; - this.#remember(); - Plugins.sort(); + this.installed = true + this.#remember() + Plugins.sort() } async loadFromURL(url: string, first: boolean = false) { if (first) { if (isApp) { - if (!confirm(tl('message.load_plugin_app'))) return; + if (!confirm(tl('message.load_plugin_app'))) return } else { - if (!confirm(tl('message.load_plugin_web'))) return; + if (!confirm(tl('message.load_plugin_web'))) return } } this.id = pathToName(url) - Plugins.registered[this.id] = this; + Plugins.registered[this.id] = this Plugins.all.safePush(this) - this.tags.safePush('Remote'); + this.tags.safePush('Remote') - this.source = 'url'; - let content = await this.#runPluginFile(url).catch(async (error) => { + this.source = 'url' + let content = await this.#runPluginFile(url).catch(async error => { if (isApp) { - await this.load(); + await this.load() } - console.error(error); + console.error(error) }) if (content) { if (first && this.oninstall) { @@ -543,49 +585,51 @@ export class Plugin { // Save if (isApp) { await new Promise((resolve, reject) => { - let file = fs.createWriteStream(Plugins.path+this.id+'.js') + let file = fs.createWriteStream(Plugins.path + this.id + '.js') // @ts-ignore - https.get(url, (response) => { - response.pipe(file); - response.on('end', resolve) - }).on('error', reject); + https + .get(url, response => { + response.pipe(file) + response.on('end', resolve) + }) + .on('error', reject) }) } } - return this; + return this } #remember(id = this.id, path = this.path) { - let entry = Plugins.installed.find(plugin => plugin.id == this.id); - let already_exists = !!entry; - if (!entry) entry = {} as PluginInstallation; + let entry = Plugins.installed.find(plugin => plugin.id == this.id) + let already_exists = !!entry + if (!entry) entry = {} as PluginInstallation - entry.id = id; - entry.version = this.version; - entry.path = path; - entry.source = this.source; - entry.disabled = this.disabled ? true : undefined; - entry.dependencies = this.dependencies?.length ? this.dependencies.slice() : undefined; + entry.id = id + entry.version = this.version + entry.path = path + entry.source = this.source + entry.disabled = this.disabled ? true : undefined + entry.dependencies = this.dependencies?.length ? this.dependencies.slice() : undefined - if (!already_exists) Plugins.installed.push(entry); + if (!already_exists) Plugins.installed.push(entry) StateMemory.save('installed_plugins') - return this; + return this } uninstall() { try { - this.unload(); + this.unload() if (this.onuninstall) { - this.onuninstall(); + this.onuninstall() } } catch (err) { - console.error(`Error in unload or uninstall method of "${this.id}": `, err); + console.error(`Error in unload or uninstall method of "${this.id}": `, err) } - delete Plugins.registered[this.id]; - let in_installed = Plugins.installed.find(plugin => plugin.id == this.id); - Plugins.installed.remove(in_installed); + delete Plugins.registered[this.id] + let in_installed = Plugins.installed.find(plugin => plugin.id == this.id) + Plugins.installed.remove(in_installed) StateMemory.save('installed_plugins') - this.installed = false; - this.disabled = false; + this.installed = false + this.disabled = false if (isApp && this.source !== 'store') { Plugins.all.remove(this) @@ -593,212 +637,217 @@ export class Plugin { if (isApp && this.source != 'file') { function removeCachedFile(filepath) { if (fs.existsSync(filepath)) { - fs.unlink(filepath, (err) => { - if (err) console.log(err); - }); + fs.unlink(filepath, err => { + if (err) console.log(err) + }) } } - removeCachedFile(Plugins.path + this.id + '.js'); - removeCachedFile(Plugins.path + this.id + '.' + this.icon); - removeCachedFile(Plugins.path + this.id + '.about.md'); + removeCachedFile(Plugins.path + this.id + '.js') + removeCachedFile(Plugins.path + this.id + '.' + this.icon) + removeCachedFile(Plugins.path + this.id + '.about.md') } StateMemory.save('installed_plugins') - return this; + return this } unload() { if (this.onunload) { this.onunload() } - return this; + return this } reload() { - if (!isApp && this.source == 'file') return this; + if (!isApp && this.source == 'file') return this - this.cache_version++; + this.cache_version++ this.unload() - this.tags.empty(); - this.contributors.empty(); - this.dependencies.empty(); - Plugins.all.remove(this); - this.details = null; - let had_changelog = this.changelog_fetched; - this.changelog_fetched = false; + this.tags.empty() + this.contributors.empty() + this.dependencies.empty() + Plugins.all.remove(this) + this.details = null + let had_changelog = this.changelog_fetched + this.changelog_fetched = false if (this.source == 'file') { - this.loadFromFile({path: this.path, name: this.path, content: ''}, false) - + this.loadFromFile({ path: this.path, name: this.path, content: '' }, false) } else if (this.source == 'url') { this.loadFromURL(this.path, false) } - this.fetchAbout(true); + this.fetchAbout(true) if (had_changelog && this.has_changelog) { - this.fetchChangelog(true); + this.fetchChangelog(true) } - return this; + return this } async #runPluginFile(path: string) { - let file_content: any; + let file_content: any if (path.startsWith('http')) { if (!path.startsWith('https')) { - throw 'Cannot load plugins over http: ' + path; + throw 'Cannot load plugins over http: ' + path } await new Promise((resolve, reject) => { $.ajax({ cache: false, url: path, success(data) { - file_content = data; - resolve(); + file_content = data + resolve() }, error() { - reject('Failed to load plugin ' + this.id); - } - }); + reject('Failed to load plugin ' + this.id) + }, + }) }) - } else if (isApp) { - file_content = fs.readFileSync(path, {encoding: 'utf-8'}); - + file_content = fs.readFileSync(path, { encoding: 'utf-8' }) } else { throw 'Failed to load plugin: Unknown URL format' } - this.#runCode(file_content); - return file_content; + this.#runCode(file_content) + return file_content } #runCode(code: string) { if (typeof code != 'string' || code.length < 20) { - throw `Issue loading plugin "${this.id}": Plugin file empty`; + throw `Issue loading plugin "${this.id}": Plugin file empty` } try { - const func = new Function('requireNativeModule', 'require', code + `\n//# sourceURL=PLUGINS/(Plugin):${this.id}.js`); - const scoped_require = isApp ? getPluginScopedRequire(this) : undefined; - func(scoped_require, scoped_require); + const func = new Function( + 'requireNativeModule', + 'require', + code + `\n//# sourceURL=PLUGINS/(Plugin):${this.id}.js` + ) + const scoped_require = isApp ? getPluginScopedRequire(this) : undefined + func(scoped_require, scoped_require) } catch (err) { - console.error(err); + console.error(err) } } toggleDisabled() { if (!this.disabled) { - this.disabled = true; + this.disabled = true this.unload() } else { if (this.onload) { this.onload() } - this.disabled = false; + this.disabled = false } - this.#remember(); + this.#remember() } showContextMenu(event: MouseEvent) { - Plugin.menu.open(event, this); + Plugin.menu.open(event, this) } isReloadable() { - return this.installed && !this.disabled && ((this.source == 'file' && isApp) || (this.source == 'url')); + return ( + this.installed && + !this.disabled && + ((this.source == 'file' && isApp) || this.source == 'url') + ) } isInstallable() { - var scope = this; - var result: string | boolean = + var scope = this + var result: string | boolean = scope.variant === 'both' || - ( - isApp === (scope.variant === 'desktop') && - isApp !== (scope.variant === 'web') - ); + (isApp === (scope.variant === 'desktop') && isApp !== (scope.variant === 'web')) if (result && scope.min_version) { - result = Blockbench.isOlderThan(scope.min_version) ? 'outdated_client' : true; + result = Blockbench.isOlderThan(scope.min_version) ? 'outdated_client' : true } if (result && scope.max_version) { result = Blockbench.isNewerThan(scope.max_version) ? 'outdated_plugin' : true } if (result === false) { - result = (scope.variant === 'web') ? 'web_only' : 'app_only' + result = scope.variant === 'web' ? 'web_only' : 'app_only' } - return (result === true) ? true : tl('dialog.plugins.'+result); + return result === true ? true : tl('dialog.plugins.' + result) } hasImageIcon() { - return this.icon.endsWith('.png') || this.icon.endsWith('.svg'); + return this.icon.endsWith('.png') || this.icon.endsWith('.svg') } getIcon() { if (this.hasImageIcon()) { if (isApp) { if (this.installed && this.source == 'store') { - return Plugins.path + this.id + '.' + this.icon; + return Plugins.path + this.id + '.' + this.icon } if (this.source != 'store') - return this.path.replace(/\w+\.js$/, this.icon + (this.cache_version ? '?'+this.cache_version : '')); - } - return `${Plugins.api_path}/${this.id}/${this.icon}`; + return this.path.replace( + /\w+\.js$/, + this.icon + (this.cache_version ? '?' + this.cache_version : '') + ) + } + return `${Plugins.api_path}/${this.id}/${this.icon}` } - return this.icon; + return this.icon } async fetchAbout(force: boolean = false) { if (((!this.about_fetched && !this.about) || force) && this.new_repository_format) { if (isApp && this.installed) { try { - let about_path; + let about_path if (this.source == 'store') { - about_path = PathModule.join(Plugins.path, this.id + '.about.md'); + about_path = PathModule.join(Plugins.path, this.id + '.about.md') } else { - about_path = this.path.replace(/\w+\.js$/, 'about.md'); + about_path = this.path.replace(/\w+\.js$/, 'about.md') } - let content = fs.readFileSync(about_path, {encoding: 'utf-8'}); - this.about = content; - this.about_fetched = true; - return; + let content = fs.readFileSync(about_path, { encoding: 'utf-8' }) + this.about = content + this.about_fetched = true + return } catch (err) { - console.error('failed to get about for plugin ' + this.id); + console.error('failed to get about for plugin ' + this.id) } } - let url = `${Plugins.api_path}/${this.id}/about.md`; + let url = `${Plugins.api_path}/${this.id}/about.md` let result = await fetch(url).catch(() => { - console.error('about.md missing for plugin ' + this.id); - }); + console.error('about.md missing for plugin ' + this.id) + }) if (result && result.ok) { - this.about = await result.text(); + this.about = await result.text() } - this.about_fetched = true; + this.about_fetched = true } } async fetchChangelog(force = false) { if ((!this.changelog_fetched && !this.changelog) || force) { function reverseOrder(input) { - let output = {}; + let output = {} Object.keys(input).forEachReverse(key => { - output[key] = input[key]; + output[key] = input[key] }) - return output; + return output } if (isApp && this.installed && this.source != 'store') { try { - let changelog_path = this.path.replace(/\w+\.js$/, 'changelog.json'); - let content = fs.readFileSync(changelog_path, {encoding: 'utf-8'}); - this.changelog = reverseOrder(JSON.parse(content)); - this.changelog_fetched = true; - return; + let changelog_path = this.path.replace(/\w+\.js$/, 'changelog.json') + let content = fs.readFileSync(changelog_path, { encoding: 'utf-8' }) + this.changelog = reverseOrder(JSON.parse(content)) + this.changelog_fetched = true + return } catch (err) { - console.error('failed to get changelog for plugin ' + this.id, err); + console.error('failed to get changelog for plugin ' + this.id, err) } } - let url = `${Plugins.api_path}/${this.id}/changelog.json`; + let url = `${Plugins.api_path}/${this.id}/changelog.json` let result = await fetch(url).catch(() => { - console.error('changelog.json missing for plugin ' + this.id); - }); + console.error('changelog.json missing for plugin ' + this.id) + }) if (result && result.ok) { - this.changelog = reverseOrder(await result.json()); + this.changelog = reverseOrder(await result.json()) } - this.changelog_fetched = true; + this.changelog_fetched = true } } getPluginDetails(force_refresh: boolean = false) { - if (this.details && !force_refresh) return this.details; + if (this.details && !force_refresh) return this.details this.details = { version: this.version, last_modified: 'N/A', creation_date: 'N/A', last_modified_full: '', creation_date_full: '', - min_version: this.min_version ? (this.min_version+'+') : '-', + min_version: this.min_version ? this.min_version + '+' : '-', max_version: this.max_version || '', website: this.website || '', repository: this.repository || '', @@ -808,59 +857,61 @@ export class Plugin { variant: this.variant == 'both' ? 'All' : this.variant, permissions: '', weekly_installations: separateThousands(Plugins.download_stats[this.id] || 0), - }; + } if (isApp) { - let perms = getPluginPermissions(this); + let perms = getPluginPermissions(this) if (perms) { - let perms_list = []; + let perms_list = [] for (let key in perms) { if (key == 'fs' && perms[key].directories) { - perms_list.push(`Scoped FS (${perms[key].directories.join(', ')})`); + perms_list.push(`Scoped FS (${perms[key].directories.join(', ')})`) } else { - perms_list.push(key); + perms_list.push(key) } } - this.details.permissions = perms_list.join(', '); + this.details.permissions = perms_list.join(', ') } } let trackDate = (input_date, key) => { - let date = getDateDisplay(input_date); - this.details[key] = date.short; - this.details[key + '_full'] = date.full; + let date = getDateDisplay(input_date) + this.details[key] = date.short + this.details[key + '_full'] = date.full } if (this.source == 'store') { if (!this.details.bug_tracker) { - this.details.bug_tracker = `https://github.com/JannisX11/blockbench-plugins/issues/new?title=[${this.title.replace(/[+&]/g, 'and')}]`; + this.details.bug_tracker = `https://github.com/JannisX11/blockbench-plugins/issues/new?title=[${this.title.replace(/[+&]/g, 'and')}]` } if (!this.details.repository) { - this.details.repository = `https://github.com/JannisX11/blockbench-plugins/tree/master/plugins/${this.id + (this.new_repository_format ? '' : '.js')}`; + this.details.repository = `https://github.com/JannisX11/blockbench-plugins/tree/master/plugins/${this.id + (this.new_repository_format ? '' : '.js')}` } - let github_path = (this.new_repository_format ? (this.id+'/'+this.id) : this.id) + '.js'; - let commit_url = `https://api.github.com/repos/JannisX11/blockbench-plugins/commits?path=plugins/${github_path}`; - fetch(commit_url).catch((err) => { - console.error('Cannot access commit info for ' + this.id, err); - }).then(async response => { - if (!response) return; - let commits = await response.json().catch(err => console.error(err)); - if (!commits || !commits.length) return; - trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified'); - - if (!this.creation_date) { - trackDate(Date.parse(commits.last().commit.committer.date), 'creation_date'); - } - }); - + let github_path = + (this.new_repository_format ? this.id + '/' + this.id : this.id) + '.js' + let commit_url = `https://api.github.com/repos/JannisX11/blockbench-plugins/commits?path=plugins/${github_path}` + fetch(commit_url) + .catch(err => { + console.error('Cannot access commit info for ' + this.id, err) + }) + .then(async response => { + if (!response) return + let commits = await response.json().catch(err => console.error(err)) + if (!commits || !commits.length) return + trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified') + + if (!this.creation_date) { + trackDate(Date.parse(commits.last().commit.committer.date), 'creation_date') + } + }) } if (this.creation_date) { - trackDate(this.creation_date, 'creation_date'); + trackDate(this.creation_date, 'creation_date') } - return this.details; + return this.details } - static selected: Plugin|null = null - + static selected: Plugin | null = null + static menu = new Menu([ new MenuSeparator('general'), { @@ -868,279 +919,290 @@ export class Plugin { icon: 'share', condition: plugin => Plugins.json[plugin.id], click(plugin) { - let url = `https://www.blockbench.net/plugins/${plugin.id}`; + let url = `https://www.blockbench.net/plugins/${plugin.id}` new Dialog('share_plugin', { title: tl('generic.share') + ': ' + plugin.title, icon: 'extension', form: { - link: {type: 'text', value: url, readonly: true, share_text: true} - } - }).show(); - } + link: { type: 'text', value: url, readonly: true, share_text: true }, + }, + }).show() + }, }, new MenuSeparator('installation'), { name: 'dialog.plugins.install', icon: 'add', - condition: plugin => (!plugin.installed && plugin.isInstallable() == true), + condition: plugin => !plugin.installed && plugin.isInstallable() == true, click(plugin) { - plugin.install(); - } + plugin.install() + }, }, { name: 'dialog.plugins.uninstall', icon: 'delete', - condition: plugin => (plugin.installed), + condition: plugin => plugin.installed, click(plugin) { - plugin.uninstall(); - } + plugin.uninstall() + }, }, { name: 'dialog.plugins.disable', icon: 'bedtime', - condition: plugin => (plugin.installed && !plugin.disabled), + condition: plugin => plugin.installed && !plugin.disabled, click(plugin) { - plugin.toggleDisabled(); - } + plugin.toggleDisabled() + }, }, { name: 'dialog.plugins.enable', icon: 'bedtime', - condition: plugin => (plugin.installed && plugin.disabled), + condition: plugin => plugin.installed && plugin.disabled, click(plugin) { - plugin.toggleDisabled(); - } + plugin.toggleDisabled() + }, }, { name: 'dialog.plugins.revoke_permissions', icon: 'key_off', condition: isApp && ((plugin: Plugin) => getPluginPermissions(plugin)), click(plugin: Plugin) { - let revoked = revokePluginPermissions(plugin); - Blockbench.showQuickMessage(`Revoked ${revoked.length} permissions. Restart to apply`, 2000); - plugin.getPluginDetails(true); - } + let revoked = revokePluginPermissions(plugin) + Blockbench.showQuickMessage( + `Revoked ${revoked.length} permissions. Restart to apply`, + 2000 + ) + plugin.getPluginDetails(true) + }, }, new MenuSeparator('developer'), { name: 'dialog.plugins.reload', icon: 'refresh', - condition: plugin => (plugin.installed && plugin.isReloadable()), + condition: plugin => plugin.installed && plugin.isReloadable(), click(plugin) { - plugin.reload(); - } + plugin.reload() + }, }, { name: 'menu.animation.open_location', icon: 'folder', - condition: plugin => (isApp && plugin.source == 'file'), + condition: plugin => isApp && plugin.source == 'file', click(plugin) { - Filesystem.showFileInFolder(plugin.path); - } + Filesystem.showFileInFolder(plugin.path) + }, }, ]) static register(id: string, data: PluginOptions) { if (typeof id !== 'string' || typeof data !== 'object') { console.warn('Plugin.register: not enough arguments, string and object required.') - return; + return } - var plugin = Plugins.registered[id]; + var plugin = Plugins.registered[id] if (!plugin) { - plugin = Plugins.registered.unknown; + plugin = Plugins.registered.unknown if (plugin) { - delete Plugins.registered.unknown; - plugin.id = id; - Plugins.registered[id] = plugin; + delete Plugins.registered.unknown + plugin.id = id + Plugins.registered[id] = plugin } } if (!plugin) { Blockbench.showMessageBox({ translateKey: 'load_plugin_failed', - message: tl('message.load_plugin_failed.message', [id]) + message: tl('message.load_plugin_failed.message', [id]), }) - return; - }; + return + } plugin.extend(data) if (plugin.isInstallable() == true && plugin.disabled == false) { if (plugin.onload instanceof Function) { - Plugins.currently_loading = id; - plugin.onload(); - Plugins.currently_loading = ''; + Plugins.currently_loading = id + plugin.onload() + Plugins.currently_loading = '' } } - return plugin; + return plugin } } - // Alias for typescript -export const BBPlugin = Plugin; - +export const BBPlugin = Plugin if (isApp) { - Plugins.path = app.getPath('userData')+osfs+'plugins'+osfs - fs.readdir(Plugins.path, function(err) { + Plugins.path = app.getPath('userData') + osfs + 'plugins' + osfs + fs.readdir(Plugins.path, function (err) { if (err) { - fs.mkdir(Plugins.path, function(a) {}) + fs.mkdir(Plugins.path, function (a) {}) } }) } else { - Plugins.path = Plugins.api_path+'/'; + Plugins.path = Plugins.api_path + '/' } Plugins.loading_promise = new Promise((resolve, reject) => { $.ajax({ cache: false, - url: Plugins.api_path+'.json', + url: Plugins.api_path + '.json', dataType: 'json', success(data) { - Plugins.json = data; - - resolve(); - Plugins.loading_promise = null; + Plugins.json = data + + resolve() + Plugins.loading_promise = null }, error() { console.log('Could not connect to plugin server') $('#plugin_available_empty').text('Could not connect to plugin server') - resolve(); - Plugins.loading_promise = null; + resolve() + Plugins.loading_promise = null if (settings.cdn_mirror.value == false && navigator.onLine) { - settings.cdn_mirror.set(true); - console.log('Switching to plugin CDN mirror. Restart to apply.'); + settings.cdn_mirror.set(true) + console.log('Switching to plugin CDN mirror. Restart to apply.') } - } - }); + }, + }) }) $.getJSON('https://blckbn.ch/api/stats/plugins?weeks=2', data => { - Plugins.download_stats = data; + Plugins.download_stats = data if (Plugins.json) { - Plugins.sort(); + Plugins.sort() } }) export async function loadInstalledPlugins() { if (Plugins.loading_promise) { - await Plugins.loading_promise; + await Plugins.loading_promise } - const install_promises = []; - const online_access = Plugins.json instanceof Object && navigator.onLine; + const install_promises = [] + const online_access = Plugins.json instanceof Object && navigator.onLine // Setup offers from store if (online_access) { for (let id in Plugins.json) { - new Plugin(id, Plugins.json[id]); + new Plugin(id, Plugins.json[id]) } - Plugins.sort(); + Plugins.sort() } // Load plugins if (Plugins.installed.length > 0) { - // Resolve dependency order // TODO: solve dependency order on plugins that load asynchronously (on update from web etc.) function resolveDependencies(installation: PluginInstallation, depth) { - if (depth > 10) { - console.error(`Could not resolve plugin dependencies: Recursive dependency on plugin "${installation.id}"`, installation); - return; + if (depth > 10) { + console.error( + `Could not resolve plugin dependencies: Recursive dependency on plugin "${installation.id}"`, + installation + ) + return } for (let dependency_id of installation.dependencies) { - let dependency_installation = Plugins.installed.find(inst => inst.id == dependency_id); - let this_index = Plugins.installed.indexOf(installation); - let dep_index = Plugins.installed.indexOf(dependency_installation); + let dependency_installation = Plugins.installed.find( + inst => inst.id == dependency_id + ) + let this_index = Plugins.installed.indexOf(installation) + let dep_index = Plugins.installed.indexOf(dependency_installation) if (dependency_installation && dep_index > this_index) { - Plugins.installed.remove(dependency_installation); - Plugins.installed.splice(this_index, 0, dependency_installation); + Plugins.installed.remove(dependency_installation) + Plugins.installed.splice(this_index, 0, dependency_installation) if (dependency_installation.dependencies?.length) { - resolveDependencies(dependency_installation, depth+1); + resolveDependencies(dependency_installation, depth + 1) } } } } for (let installation of Plugins.installed.slice()) { if (installation.dependencies?.length) { - resolveDependencies(installation, 0); + resolveDependencies(installation, 0) } } // Install plugins - var load_counter = 0; + var load_counter = 0 Plugins.installed.slice().forEach(function loadPlugin(installation) { - if (installation.source == 'file') { // Dev Plugins if (isApp && fs.existsSync(installation.path)) { - var instance = new Plugin(installation.id, {disabled: installation.disabled}); - install_promises.push(instance.loadFromFile({path: installation.path, name: installation.path, content: ''}, false)); - load_counter++; - console.log(`🧩📁 Loaded plugin "${installation.id || installation.path}" from file`); + var instance = new Plugin(installation.id, { disabled: installation.disabled }) + install_promises.push( + instance.loadFromFile( + { path: installation.path, name: installation.path, content: '' }, + false + ) + ) + load_counter++ + console.log( + `🧩📁 Loaded plugin "${installation.id || installation.path}" from file` + ) } else { - Plugins.installed.remove(installation); + Plugins.installed.remove(installation) } - } else if (installation.source == 'url') { // URL if (installation.path) { - var instance = new Plugin(installation.id, {disabled: installation.disabled}); - install_promises.push(instance.loadFromURL(installation.path, false)); - load_counter++; - console.log(`🧩🌐 Loaded plugin "${installation.id || installation.path}" from URL`); + var instance = new Plugin(installation.id, { disabled: installation.disabled }) + install_promises.push(instance.loadFromURL(installation.path, false)) + load_counter++ + console.log( + `🧩🌐 Loaded plugin "${installation.id || installation.path}" from URL` + ) } else { - Plugins.installed.remove(installation); + Plugins.installed.remove(installation) } - } else if (online_access) { // Store plugin - let plugin = Plugins.all.find(p => p.id == installation.id); + let plugin = Plugins.all.find(p => p.id == installation.id) if (plugin) { - plugin.installed = true; - if (installation.disabled) plugin.disabled = true; - - if (isApp && ( - (installation.version && plugin.version && !compareVersions(plugin.version, installation.version)) || - Blockbench.isOlderThan(plugin.min_version) - )) { + plugin.installed = true + if (installation.disabled) plugin.disabled = true + + if ( + isApp && + ((installation.version && + plugin.version && + !compareVersions(plugin.version, installation.version)) || + Blockbench.isOlderThan(plugin.min_version)) + ) { // Get from file - let promise = plugin.load(false); - install_promises.push(promise); + let promise = plugin.load(false) + install_promises.push(promise) } else { // Update - let promise = plugin.download(); + let promise = plugin.download() if (plugin.await_loading) { - install_promises.push(promise); + install_promises.push(promise) } } - load_counter++; - console.log(`🧩🛒 Loaded plugin "${installation.id}" from store`); - + load_counter++ + console.log(`🧩🛒 Loaded plugin "${installation.id}" from store`) } else if (Plugins.json instanceof Object && navigator.onLine) { - Plugins.installed.remove(installation); + Plugins.installed.remove(installation) } - } else if (isApp && installation.source == 'store') { // Offline install store plugin - let plugin = new Plugin(installation.id); - let promise = plugin.load(false); - install_promises.push(promise); + let plugin = new Plugin(installation.id) + let promise = plugin.load(false) + install_promises.push(promise) } else { - Plugins.installed.remove(installation); + Plugins.installed.remove(installation) } }) console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`) } StateMemory.save('installed_plugins') - install_promises.forEach(promise => { - promise.catch(console.error); + promise.catch(console.error) }) - return await Promise.allSettled(install_promises); + return await Promise.allSettled(install_promises) } -BARS.defineActions(function() { - let actions_setup = false; +BARS.defineActions(function () { + let actions_setup = false Plugins.dialog = new Dialog({ id: 'plugins', title: 'dialog.plugins.title', @@ -1149,9 +1211,11 @@ BARS.defineActions(function() { resizable: 'xy', onOpen() { if (!actions_setup) { - BarItems.load_plugin.toElement(document.getElementById('plugins_list_main_bar')); - BarItems.load_plugin_from_url.toElement(document.getElementById('plugins_list_main_bar')); - actions_setup = true; + BarItems.load_plugin.toElement(document.getElementById('plugins_list_main_bar')) + BarItems.load_plugin_from_url.toElement( + document.getElementById('plugins_list_main_bar') + ) + actions_setup = true } }, component: { @@ -1169,7 +1233,7 @@ BARS.defineActions(function() { }, computed: { plugin_search() { - let search_name = this.search_term.toUpperCase(); + let search_name = this.search_term.toUpperCase() if (search_name) { let filtered = this.items.filter(item => { return ( @@ -1179,113 +1243,132 @@ BARS.defineActions(function() { item.author.toUpperCase().includes(search_name) || item.tags.find(tag => tag.toUpperCase().includes(search_name)) ) - }); - let installed = filtered.filter(p => p.installed); - let not_installed = filtered.filter(p => !p.installed); - return installed.concat(not_installed); + }) + let installed = filtered.filter(p => p.installed) + let not_installed = filtered.filter(p => !p.installed) + return installed.concat(not_installed) } else { return this.items.filter(item => { - return (this.tab == 'installed') == item.installed; + return (this.tab == 'installed') == item.installed }) } }, suggested_rows() { - let tags = ["Animation"]; + let tags = ['Animation'] this.items.forEach(plugin => { - if (!plugin.installed) return; + if (!plugin.installed) return tags.safePush(...plugin.tags) }) - let rows = tags.map(tag => { - let plugins = this.items.filter(plugin => !plugin.installed && plugin.tags.includes(tag) && !plugin.tags.includes('Deprecated')).slice(0, 12); - return { - title: tag, - plugins, - } - }).filter(row => row.plugins.length > 2); + let rows = tags + .map(tag => { + let plugins = this.items + .filter( + plugin => + !plugin.installed && + plugin.tags.includes(tag) && + !plugin.tags.includes('Deprecated') + ) + .slice(0, 12) + return { + title: tag, + plugins, + } + }) + .filter(row => row.plugins.length > 2) //rows.sort((a, b) => a.plugins.length - b.plugins.length); - rows.sort(() => Math.random() - 0.5); - - let cutoff = Date.now() - (3_600_000 * 24 * 28); - let new_plugins = this.items.filter(plugin => !plugin.installed && plugin.creation_date > cutoff && !plugin.tags.includes('Deprecated')); + rows.sort(() => Math.random() - 0.5) + + let cutoff = Date.now() - 3_600_000 * 24 * 28 + let new_plugins = this.items.filter( + plugin => + !plugin.installed && + plugin.creation_date > cutoff && + !plugin.tags.includes('Deprecated') + ) if (new_plugins.length) { - new_plugins.sort((a, b) => a.creation_date - b.creation_date); + new_plugins.sort((a, b) => a.creation_date - b.creation_date) let new_row = { title: 'New', - plugins: new_plugins.slice(0, 12) + plugins: new_plugins.slice(0, 12), } - rows.splice(0, 0, new_row); + rows.splice(0, 0, new_row) } - return rows.slice(0, 3); + return rows.slice(0, 3) }, viewed_plugins() { - return this.plugin_search.slice(this.page * this.per_page, (this.page+1) * this.per_page); + return this.plugin_search.slice( + this.page * this.per_page, + (this.page + 1) * this.per_page + ) }, pages() { - let pages = []; - let length = this.plugin_search.length; + let pages = [] + let length = this.plugin_search.length for (let i = 0; i * this.per_page < length; i++) { - pages.push(i); + pages.push(i) } - return pages; + return pages }, selected_plugin_settings() { - if (!this.selected_plugin) return {}; - let plugin_settings = {}; + if (!this.selected_plugin) return {} + let plugin_settings = {} for (let id in this.settings) { if (settings[id].plugin == this.selected_plugin.id) { - plugin_settings[id] = settings[id]; + plugin_settings[id] = settings[id] } } - return plugin_settings; - } + return plugin_settings + }, }, methods: { setTab(tab) { - this.tab = tab; - this.setPage(0); + this.tab = tab + this.setPage(0) }, setPage(number) { - this.page = number; - this.$refs.plugin_list.scrollTop = 0; + this.page = number + this.$refs.plugin_list.scrollTop = 0 }, selectPlugin(plugin: Plugin) { if (!plugin) { - this.selected_plugin = Plugin.selected = null; - return; + this.selected_plugin = Plugin.selected = null + return } - plugin.fetchAbout(); - this.selected_plugin = Plugin.selected = plugin; + plugin.fetchAbout() + this.selected_plugin = Plugin.selected = plugin if (!this.selected_plugin.installed && this.page_tab == 'settings') { - this.page_tab == 'about'; + this.page_tab == 'about' } if (this.page_tab == 'changelog') { if (plugin.has_changelog) { - plugin.fetchChangelog(); + plugin.fetchChangelog() } else { - this.page_tab == 'about'; + this.page_tab == 'about' } } }, setPageTab(tab: string) { - this.page_tab = tab; + this.page_tab = tab if (this.page_tab == 'changelog' && this.selected_plugin.has_changelog) { - this.selected_plugin.fetchChangelog(); + this.selected_plugin.fetchChangelog() } }, showDependency(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency); + let plugin = Plugins.all.find(p => p.id == dependency) if (plugin) { - this.selectPlugin(plugin); + this.selectPlugin(plugin) } }, getDependencyName(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency); - return plugin ? (plugin.title + (plugin.installed ? ' ✓' : '')) : (dependency + ' ⚠'); + let plugin = Plugins.all.find(p => p.id == dependency) + return plugin + ? plugin.title + (plugin.installed ? ' ✓' : '') + : dependency + ' ⚠' }, isDependencyInstalled(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency); - return plugin && plugin.installed; + let plugin = Plugins.all.find(p => p.id == dependency) + return plugin && plugin.installed }, getTagClass(tag: string): string { if (tag.match(/^(local|remote)$/i)) { @@ -1297,50 +1380,54 @@ BARS.defineActions(function() { } }, formatAbout(about: string) { - return pureMarked(about); + return pureMarked(about) }, reduceLink(url: string): string { - url = url.replace('https://', '').replace(/\/$/, ''); + url = url.replace('https://', '').replace(/\/$/, '') if (url.length > 50) { - return url.substring(0, 50)+'...'; + return url.substring(0, 50) + '...' } else { - return url; + return url } }, printDate(input_date: number) { - return getDateDisplay(input_date).short; + return getDateDisplay(input_date).short }, printDateFull(input_date: number) { - return getDateDisplay(input_date).full; + return getDateDisplay(input_date).full }, formatChangelogLine(line) { - let content = []; - let last_i = 0; + let content = [] + let last_i = 0 for (let match of line.matchAll(/\[.+?\]\(.+?\)/g)) { - let split = match[0].search(/\]\(/); - let label = match[0].substring(1, split); - let href = match[0].substring(split+2, match[0].length-1); - let a = Interface.createElement('a', {href, title: href}, label); - content.push(line.substring(last_i, match.index)); - content.push(a); - last_i = match.index + match[0].length; + let split = match[0].search(/\]\(/) + let label = match[0].substring(1, split) + let href = match[0].substring(split + 2, match[0].length - 1) + let a = Interface.createElement('a', { href, title: href }, label) + content.push(line.substring(last_i, match.index)) + content.push(a) + last_i = match.index + match[0].length } - content.push(line.substring(last_i)); - let node = Interface.createElement('p', {}, content.filter(a => a)); - return node.innerHTML; + content.push(line.substring(last_i)) + let node = Interface.createElement( + 'p', + {}, + content.filter(a => a) + ) + return node.innerHTML }, // Settings changePluginSetting(setting) { setTimeout(() => { if (typeof setting.onChange == 'function') { - setting.onChange(setting.value); + setting.onChange(setting.value) } - Settings.saveLocalStorages(); - }, 20); + Settings.saveLocalStorages() + }, 20) }, openSettingInSettings(key, profile) { - Settings.openDialog({search_term: key, profile}); + Settings.openDialog({ search_term: key, profile }) }, settingContextMenu(setting, event) { new Menu([ @@ -1348,25 +1435,25 @@ BARS.defineActions(function() { name: 'dialog.settings.reset_to_default', icon: 'replay', click: () => { - setting.ui_value = setting.default_value; - Settings.saveLocalStorages(); - } - } - ]).open(event); + setting.ui_value = setting.default_value + Settings.saveLocalStorages() + }, + }, + ]).open(event) }, getProfileValuesForSetting(key) { return SettingsProfile.all.filter(profile => { - return profile.settings[key] !== undefined; - }); + return profile.settings[key] !== undefined + }) }, // Features getPluginFeatures(plugin) { - let types = []; + let types = [] - let formats = []; + let formats = [] for (let id in Formats) { - if (Formats[id].plugin == plugin.id) formats.push(Formats[id]); + if (Formats[id].plugin == plugin.id) formats.push(Formats[id]) } if (formats.length) { types.push({ @@ -1378,19 +1465,22 @@ BARS.defineActions(function() { name: format.name, icon: format.icon, description: format.description, - click: format.show_on_start_screen && (() => { - Dialog.open.close(); - StartScreen.open(); - StartScreen.vue.loadFormat(format); - }) + click: + format.show_on_start_screen && + (() => { + Dialog.open.close() + StartScreen.open() + StartScreen.vue.loadFormat(format) + }), } - }) + }), }) } - let loaders = []; + let loaders = [] for (let id in ModelLoader.loaders) { - if (ModelLoader.loaders[id].plugin == plugin.id) loaders.push(ModelLoader.loaders[id]); + if (ModelLoader.loaders[id].plugin == plugin.id) + loaders.push(ModelLoader.loaders[id]) } if (loaders.length) { types.push({ @@ -1402,19 +1492,21 @@ BARS.defineActions(function() { name: loader.name, icon: loader.icon, description: loader.description, - click: loader.show_on_start_screen && (() => { - Dialog.open.close(); - StartScreen.open(); - StartScreen.vue.loadFormat(loader); - }) + click: + loader.show_on_start_screen && + (() => { + Dialog.open.close() + StartScreen.open() + StartScreen.vue.loadFormat(loader) + }), } - }) + }), }) } - let codecs = []; + let codecs = [] for (let id in Codecs) { - if (Codecs[id].plugin == plugin.id) codecs.push(Codecs[id]); + if (Codecs[id].plugin == plugin.id) codecs.push(Codecs[id]) } if (codecs.length) { types.push({ @@ -1425,15 +1517,17 @@ BARS.defineActions(function() { id: codec.id, name: codec.name, icon: codec.export_action ? codec.export_action.icon : 'save', - description: codec.export_action ? codec.export_action.description : '' + description: codec.export_action + ? codec.export_action.description + : '', } - }) + }), }) } - let bar_items = Keybinds.actions.filter(action => action.plugin == plugin.id); - let tools = bar_items.filter(action => action instanceof Tool); - let other_actions = bar_items.filter(action => action instanceof Tool == false); + let bar_items = Keybinds.actions.filter(action => action.plugin == plugin.id) + let tools = bar_items.filter(action => action instanceof Tool) + let other_actions = bar_items.filter(action => action instanceof Tool == false) if (tools.length) { types.push({ @@ -1446,11 +1540,13 @@ BARS.defineActions(function() { icon: tool.icon, description: tool.description, extra_info: tool.keybind.label, - click: Condition(tool.condition) && (() => { - ActionControl.select(tool.name); - }) + click: + Condition(tool.condition) && + (() => { + ActionControl.select(tool.name) + }), } - }) + }), }) } if (other_actions.length) { @@ -1464,17 +1560,19 @@ BARS.defineActions(function() { icon: action.icon, description: action.description, extra_info: action.keybind.label, - click: Condition(action.condition) && (() => { - ActionControl.select(action.name); - }) + click: + Condition(action.condition) && + (() => { + ActionControl.select(action.name) + }), } - }) + }), }) } - let panels = []; + let panels = [] for (let id in Panels) { - if (Panels[id].plugin == plugin.id) panels.push(Panels[id]); + if (Panels[id].plugin == plugin.id) panels.push(Panels[id]) } if (panels.length) { types.push({ @@ -1484,15 +1582,15 @@ BARS.defineActions(function() { return { id: panel.id, name: panel.name, - icon: panel.icon + icon: panel.icon, } - }) + }), }) } - let setting_list = []; + let setting_list = [] for (let id in settings) { - if (settings[id].plugin == plugin.id) setting_list.push(settings[id]); + if (settings[id].plugin == plugin.id) setting_list.push(settings[id]) } if (setting_list.length) { types.push({ @@ -1504,14 +1602,16 @@ BARS.defineActions(function() { name: setting.name, icon: setting.icon, click: () => { - this.page_tab = 'settings'; - } + this.page_tab = 'settings' + }, } - }) + }), }) } - let validator_checks = Validator.checks.filter(check => check.plugin == plugin.id); + let validator_checks = Validator.checks.filter( + check => check.plugin == plugin.id + ) if (validator_checks.length) { types.push({ id: 'validator_checks', @@ -1520,22 +1620,22 @@ BARS.defineActions(function() { return { id: validator_check.id, name: validator_check.name, - icon: 'task_alt' + icon: 'task_alt', } - }) + }), }) } //TODO //Modes //Element Types - return types; + return types }, getIconNode: Blockbench.getIconNode, pureMarked, capitalizeFirstLetter, tl, - Condition + Condition, }, mount_directly: true, template: ` @@ -1812,84 +1912,83 @@ BARS.defineActions(function() { - ` - } + `, + }, }) new Action('plugins_window', { icon: 'extension', category: 'blockbench', - side_menu: new Menu('plugins_window', [ - 'load_plugin', - 'load_plugin_from_url' - ]), + side_menu: new Menu('plugins_window', ['load_plugin', 'load_plugin_from_url']), click(e) { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); - return; + Blockbench.showQuickMessage('message.classroom_mode.install_plugin') + return } - Plugins.dialog.show(); - let none_installed = !Plugins.all.find(plugin => plugin.installed); - if (none_installed) Plugins.dialog.content_vue.tab = 'available'; + Plugins.dialog.show() + let none_installed = !Plugins.all.find(plugin => plugin.installed) + if (none_installed) Plugins.dialog.content_vue.tab = 'available' $('dialog#plugins #plugin_search_bar input').trigger('focus') - } + }, }) new Action('reload_plugins', { icon: 'sync', category: 'blockbench', click() { Plugins.devReload() - } + }, }) new Action('load_plugin', { icon: 'fa-file-code', category: 'blockbench', click() { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); - return; + Blockbench.showQuickMessage('message.classroom_mode.install_plugin') + return } - Blockbench.import({ - resource_id: 'dev_plugin', - extensions: ['js'], - type: 'Blockbench Plugin', - }, function(files) { - new Plugin().loadFromFile(files[0], true) - }) - } + Blockbench.import( + { + resource_id: 'dev_plugin', + extensions: ['js'], + type: 'Blockbench Plugin', + }, + function (files) { + new Plugin().loadFromFile(files[0], true) + } + ) + }, }) new Action('load_plugin_from_url', { icon: 'cloud_download', category: 'blockbench', click() { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); - return; + Blockbench.showQuickMessage('message.classroom_mode.install_plugin') + return } Blockbench.textPrompt('URL', '', url => { new Plugin().loadFromURL(url, true) }) - } + }, }) new Action('add_plugin', { icon: 'add', category: 'blockbench', click() { - setTimeout(_ => ActionControl.select('+plugin: '), 1); - } + setTimeout(_ => ActionControl.select('+plugin: '), 1) + }, }) new Action('remove_plugin', { icon: 'remove', category: 'blockbench', click() { - setTimeout(_ => ActionControl.select('-plugin: '), 1); - } + setTimeout(_ => ActionControl.select('-plugin: '), 1) + }, }) }) - Object.assign(window, { Plugins, Plugin, - BBPlugin -}); + BBPlugin, +}) diff --git a/js/shaders/shader.ts b/js/shaders/shader.ts index 154fd4af1..d851a0d51 100644 --- a/js/shaders/shader.ts +++ b/js/shaders/shader.ts @@ -1,15 +1,18 @@ -import { settings } from "../interface/settings"; +import { settings } from '../interface/settings' /** * Prepare shader with the correct options depending on device and settings * @internal */ export function prepareShader(shader: string): string { - if (settings.antialiasing_bleed_fix.value == false || Preview.selected?.renderer.capabilities.isWebGL2 != true) { - shader = shader.replace(/centroid /g, ''); + if ( + settings.antialiasing_bleed_fix.value == false || + Preview.selected?.renderer.capabilities.isWebGL2 != true + ) { + shader = shader.replace(/centroid /g, '') } if (!isApp) { - shader = shader.replace('precision highp', 'precision mediump'); + shader = shader.replace('precision highp', 'precision mediump') } - return shader; -} \ No newline at end of file + return shader +} diff --git a/js/texturing/ColorPickerNormal.vue b/js/texturing/ColorPickerNormal.vue index 6d2949b0f..36d07e26f 100644 --- a/js/texturing/ColorPickerNormal.vue +++ b/js/texturing/ColorPickerNormal.vue @@ -1,63 +1,69 @@ \ No newline at end of file +.normal_map_color_picker { + position: relative; + height: 300px; +} +.normal_map_color_picker--bg_x, +.normal_map_color_picker--bg_y { + top: 5px; + left: 5px; + height: calc(100% - 10px); + width: calc(100% - 10px); + position: absolute; + pointer-events: none; + border-radius: 50%; +} +.normal_map_color_picker--bg_x { + background: linear-gradient(to right, rgb(0, 0, 255), rgb(255, 0, 255)); +} +.normal_map_color_picker--bg_y { + background: linear-gradient(to top, rgb(0, 0, 255), rgb(0, 255, 255)); + mix-blend-mode: lighten; +} +.normal_map_color_picker--bg_x::before, +.normal_map_color_picker--bg_x::after { + content: ''; + width: 2px; + height: 2px; + position: absolute; + background-color: var(--color-light); + opacity: 0.2; + z-index: 1; +} +.normal_map_color_picker--bg_x::before { + width: 100%; + top: calc(50% - 1px); + left: 0; + right: 0; +} +.normal_map_color_picker--bg_x::after { + height: 100%; + top: 0; + bottom: 0; + left: calc(50% - 1px); +} +.normal_map_color_picker--cursor { + position: absolute; + border-radius: 6px; + height: 10px; + width: 10px; + border: 1px solid var(--color-border); + background: var(--color-light); + cursor: pointer; + top: 0; + left: 0; + z-index: 2; + margin: -5px; +} + diff --git a/js/util/event_system.ts b/js/util/event_system.ts index 76f25acd8..1cfa0214d 100644 --- a/js/util/event_system.ts +++ b/js/util/event_system.ts @@ -1,4 +1,4 @@ -type EventListener = (data: any) => void; +type EventListener = (data: any) => void type Deletable = { delete(): void } @@ -6,73 +6,71 @@ type Deletable = { export class EventSystem { events: Record constructor() { - this.events = {}; + this.events = {} } dispatchEvent(event_name: string, data: any) { - var list = this.events[event_name]; - if (!list) return; + var list = this.events[event_name] + if (!list) return for (var i = 0; i < list.length; i++) { - list[i](data); + list[i](data) } } on(event_name: string, cb: EventListener): Deletable { if (typeof cb !== 'function') { - console.warn(cb, 'is not a function!'); - return; + console.warn(cb, 'is not a function!') + return } if (event_name.includes(' ')) { - let event_names = event_name.split(' '); + let event_names = event_name.split(' ') for (let name of event_names) { if (!this.events[name]) { - this.events[name] = []; + this.events[name] = [] } - this.events[name].safePush(cb); + this.events[name].safePush(cb) } return { delete: () => { for (let name of event_names) { - this.events[name].remove(cb); + this.events[name].remove(cb) } - } + }, } - } else { if (!this.events[event_name]) { - this.events[event_name] = []; + this.events[event_name] = [] } - this.events[event_name].safePush(cb); + this.events[event_name].safePush(cb) return { delete: () => { - this.events[event_name].remove(cb); - } + this.events[event_name].remove(cb) + }, } } } once(event_name: string, cb: EventListener): Deletable { if (typeof cb !== 'function') { - console.warn(cb, 'is not a function!'); - return; + console.warn(cb, 'is not a function!') + return } - let listener = (data) => { - this.removeListener(event_name, listener); - cb(data); + let listener = data => { + this.removeListener(event_name, listener) + cb(data) } - return this.on(event_name, listener); + return this.on(event_name, listener) } addListener(event_name: string, cb: EventListener) { - return this.on(event_name, cb); + return this.on(event_name, cb) } removeListener(event_name: string, cb: EventListener) { if (event_name.includes(' ')) { - let event_names = event_name.split(' '); + let event_names = event_name.split(' ') for (let name of event_names) { - if (this.events[name]) this.events[name].remove(cb); + if (this.events[name]) this.events[name].remove(cb) } - } else if (this.events[event_name]) { - this.events[event_name].remove(cb); + this.events[event_name].remove(cb) } } } // @ts-ignore -window.EventSystem = EventSystem \ No newline at end of file +window.EventSystem = EventSystem diff --git a/js/util/global.d.ts b/js/util/global.d.ts index 7a8ffa462..f6e4962a9 100644 --- a/js/util/global.d.ts +++ b/js/util/global.d.ts @@ -1,7 +1,6 @@ declare global { const Transformer: any - interface HTMLImageElement { src: string tex: THREE.Texture & { @@ -134,5 +133,4 @@ declare global { let osfs: string } -export { } - +export {} diff --git a/js/util/json.ts b/js/util/json.ts index ae4bbd8fb..754f6193f 100644 --- a/js/util/json.ts +++ b/js/util/json.ts @@ -1,4 +1,4 @@ -import LZUTF8 from '../lib/lzutf8'; +import LZUTF8 from '../lib/lzutf8' export class oneLiner { constructor(data: any) { @@ -33,104 +33,128 @@ interface JSONCompileOptions { * @returns JSON string */ export function compileJSON(object: any, options: JSONCompileOptions = {}): string { - let indentation = options.indentation; + let indentation = options.indentation if (typeof indentation !== 'string') { switch (settings.json_indentation.value) { - case 'spaces_4': indentation = ' '; break; - case 'spaces_2': indentation = ' '; break; - case 'tabs': default: indentation = '\t'; break; + case 'spaces_4': + indentation = ' ' + break + case 'spaces_2': + indentation = ' ' + break + case 'tabs': + default: + indentation = '\t' + break } } function newLine(tabs) { - if (options.small === true) {return '';} - let s = '\n'; + if (options.small === true) { + return '' + } + let s = '\n' for (let i = 0; i < tabs; i++) { - s += indentation; + s += indentation } - return s; + return s } function escape(string) { if (string.includes('\\')) { - string = string.replace(/\\/g, '\\\\'); + string = string.replace(/\\/g, '\\\\') } if (string.includes('"')) { - string = string.replace(/"/g, '\\"'); + string = string.replace(/"/g, '\\"') } if (string.includes('\n')) { - string = string.replace(/\n|\r\n/g, '\\n'); + string = string.replace(/\n|\r\n/g, '\\n') } if (string.includes('\t')) { - string = string.replace(/\t/g, '\\t'); + string = string.replace(/\t/g, '\\t') } - return string; + return string } function handleVar(o, tabs, breaks = true) { var out = '' - let type = typeof o; + let type = typeof o if (type === 'string') { //String out += '"' + escape(o) + '"' } else if (type === 'boolean') { //Boolean - out += (o ? 'true' : 'false') + out += o ? 'true' : 'false' } else if (o === null || o === Infinity || o === -Infinity) { //Null out += 'null' } else if (type === 'number') { //Number - o = (Math.round(o*100000)/100000).toString() + o = (Math.round(o * 100000) / 100000).toString() if (o == 'NaN') o = null out += o } else if (o instanceof Array) { //Array let has_content = false - let multiline = !!o.find(item => typeof item === 'object'); + let multiline = !!o.find(item => typeof item === 'object') if (!multiline) { - let length = 0; + let length = 0 o.forEach(item => { - length += typeof item === 'string' ? (item.length+4) : 3; - }); - if (length > 140) multiline = true; + length += typeof item === 'string' ? item.length + 4 : 3 + }) + if (length > 140) multiline = true } out += '[' for (var i = 0; i < o.length; i++) { - var compiled = handleVar(o[i], tabs+1) + var compiled = handleVar(o[i], tabs + 1) if (compiled) { - if (has_content) {out += ',' + ((options.small || multiline) ? '' : ' ')} - if (multiline) {out += newLine(tabs)} + if (has_content) { + out += ',' + (options.small || multiline ? '' : ' ') + } + if (multiline) { + out += newLine(tabs) + } out += compiled has_content = true } } - if (multiline) {out += newLine(tabs-1)} + if (multiline) { + out += newLine(tabs - 1) + } out += ']' } else if (type === 'object') { //Object - breaks = breaks && !(o instanceof oneLiner); + breaks = breaks && !(o instanceof oneLiner) var has_content = false out += '{' for (var key in o) { if (o.hasOwnProperty(key)) { - var compiled = handleVar(o[key], tabs+1, breaks) + var compiled = handleVar(o[key], tabs + 1, breaks) if (compiled) { - if (has_content) {out += ',' + (breaks || options.small?'':' ')} - if (breaks) {out += newLine(tabs)} + if (has_content) { + out += ',' + (breaks || options.small ? '' : ' ') + } + if (breaks) { + out += newLine(tabs) + } out += '"' + escape(key) + '":' + (options.small === true ? '' : ' ') out += compiled has_content = true } } } - if (breaks && has_content) {out += newLine(tabs-1)} + if (breaks && has_content) { + out += newLine(tabs - 1) + } out += '}' } - return out; + return out } - let file = handleVar(object, 1); - if ((settings.final_newline.value && options.final_newline != false) || options.final_newline == true) { - file += '\n'; + let file = handleVar(object, 1) + if ( + (settings.final_newline.value && options.final_newline != false) || + options.final_newline == true + ) { + file += '\n' } - return file; + return file } /** @@ -141,9 +165,9 @@ export function compileJSON(object: any, options: JSONCompileOptions = {}): stri */ export function autoParseJSON(data: string, feedback = true): any { if (data.substr(0, 4) === '') { - data = LZUTF8.decompress(data.substr(4), {inputEncoding: 'StorageBinaryString'}) + data = LZUTF8.decompress(data.substr(4), { inputEncoding: 'StorageBinaryString' }) } - if (data.charCodeAt(0) === 0xFEFF) { + if (data.charCodeAt(0) === 0xfeff) { data = data.substr(1) } try { @@ -153,49 +177,50 @@ export function autoParseJSON(data: string, feedback = true): any { try { data = JSON.parse(data) } catch (err) { - if (feedback === false) return; + if (feedback === false) return if (data.match(/\n\r?[><]{7}/)) { // @ts-ignore Blockbench.showMessageBox({ title: 'message.invalid_file.title', icon: 'fab.fa-git-alt', - message: 'message.invalid_file.merge_conflict' + message: 'message.invalid_file.merge_conflict', }) - return; + return } - let error_part = ''; + let error_part = '' function logErrantPart(whole, start, length) { var line = whole.substr(0, start).match(/\n/gm) - line = line ? line.length+1 : 1 - var result = ''; + line = line ? line.length + 1 : 1 + var result = '' var lines = whole.substr(start, length).split(/\n/gm) lines.forEach((s, i) => { - result += `#${line+i} ${s}\n` + result += `#${line + i} ${s}\n` }) - error_part = result.substr(0, result.length-1) + ' <-- HERE'; - console.log(error_part); + error_part = result.substr(0, result.length - 1) + ' <-- HERE' + console.log(error_part) } console.error(err) var length = err.toString().split('at position ')[1] if (length) { length = parseInt(length) - var start = limitNumber(length-32, 0, Infinity) + var start = limitNumber(length - 32, 0, Infinity) - logErrantPart(data, start, 1+length-start) + logErrantPart(data, start, 1 + length - start) } else if (err.toString().includes('Unexpected end of JSON input')) { - - logErrantPart(data, data.length-16, 10) + logErrantPart(data, data.length - 16, 10) } // @ts-ignore Blockbench.showMessageBox({ translateKey: 'invalid_file', icon: 'error', - message: tl('message.invalid_file.message', [err]) + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : '') + message: + tl('message.invalid_file.message', [err]) + + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : ''), }) - return; + return } } - return data; + return data } Object.assign(window, { diff --git a/js/util/molang.ts b/js/util/molang.ts index e62c60d65..f24987b3c 100644 --- a/js/util/molang.ts +++ b/js/util/molang.ts @@ -1,62 +1,62 @@ -let string_num_regex = /^-?\d+(\.\d+f?)?$/; +let string_num_regex = /^-?\d+(\.\d+f?)?$/ function isStringNumber(string: string) { - return string_num_regex.test(string); + return string_num_regex.test(string) } -const BRACKET_OPEN = '{(['; -const BRACKET_CLOSE = '})]'; +const BRACKET_OPEN = '{([' +const BRACKET_CLOSE = '})]' export function invertMolang(molang: string): string export function invertMolang(molang: number): number -export function invertMolang(molang: number|string): number|string { +export function invertMolang(molang: number | string): number | string { if (typeof molang == 'number') { - return -molang; + return -molang } - if (molang == '' || molang == '0') return molang; + if (molang == '' || molang == '0') return molang if (isStringNumber(molang)) { - let val = parseFloat(molang); - return (-val).toString(); + let val = parseFloat(molang) + return (-val).toString() } - let invert = true; - let bracket_depth = 0; - let result = ''; + let invert = true + let bracket_depth = 0 + let result = '' for (let char of molang) { if (!bracket_depth) { if (char == '-') { - if (!invert) result += '+'; - invert = false; - continue; + if (!invert) result += '+' + invert = false + continue } else if (char == '+') { - result += '-'; - invert = false; - continue; + result += '-' + invert = false + continue } else if ('?:'.includes(char)) { - invert = true; + invert = true } else if (invert && char != ' ') { - result += '-'; - invert = false; + result += '-' + invert = false } } if (BRACKET_OPEN.includes(char)) { - bracket_depth++; + bracket_depth++ } else if (BRACKET_CLOSE.includes(char)) { - bracket_depth--; + bracket_depth-- } - result += char; + result += char } - return result; + return result } function testInvertMolang(input: string) { - let positive_result = Animator.MolangParser.parse(input); - let inverted = invertMolang(input); - let negative_result = Animator.MolangParser.parse(inverted); + let positive_result = Animator.MolangParser.parse(input) + let inverted = invertMolang(input) + let negative_result = Animator.MolangParser.parse(inverted) if (positive_result == -negative_result) { return inverted } else { - console.warn([positive_result, negative_result], inverted); + console.warn([positive_result, negative_result], inverted) } } Object.assign(window, { invertMolang, - testInvertMolang + testInvertMolang, }) diff --git a/js/util/property.ts b/js/util/property.ts index 6ee2869f4..640f4e193 100644 --- a/js/util/property.ts +++ b/js/util/property.ts @@ -1,4 +1,4 @@ -import { FormElementOptions } from "../interface/form" +import { FormElementOptions } from '../interface/form' interface PropertyOptions { default?: any @@ -22,7 +22,7 @@ interface PropertyOptions { merge_validation?(value: any): boolean inputs?: { element_panel: { - input: FormElementOptions, + input: FormElementOptions onChange?: () => void } } @@ -46,7 +46,6 @@ interface IPropertyType { * Creates a new property on the specified target class */ export class Property implements Deletable { - class: any name: string type: T @@ -77,170 +76,212 @@ export class Property implements Deletable { constructor(target_class: any, type: T, name: string, options: PropertyOptions = {}) { if (!target_class.properties) { - target_class.properties = {}; + target_class.properties = {} } - target_class.properties[name] = this; + target_class.properties[name] = this - this.class = target_class; - this.name = name; - this.type = type; + this.class = target_class + this.name = name + this.type = type if (options.default != undefined) { - this.default = options.default; + this.default = options.default } else { switch (this.type) { - case 'string': this.default = '' as any; break; - case 'enum': this.default = options.values?.[0] || '' as any; break; - case 'molang': this.default = '0' as any; break; - case 'number': this.default = 0 as any; break; - case 'boolean': this.default = false as any; break; - case 'array': this.default = [] as any; break; - case 'object': this.default = {} as any; break; - case 'instance': this.default = null as any; break; - case 'vector': this.default = [0, 0, 0] as any; break; - case 'vector2': this.default = [0, 0] as any; break; - case 'vector4': this.default = [0, 0, 0, 0] as any; break; + case 'string': + this.default = '' as any + break + case 'enum': + this.default = options.values?.[0] || ('' as any) + break + case 'molang': + this.default = '0' as any + break + case 'number': + this.default = 0 as any + break + case 'boolean': + this.default = false as any + break + case 'array': + this.default = [] as any + break + case 'object': + this.default = {} as any + break + case 'instance': + this.default = null as any + break + case 'vector': + this.default = [0, 0, 0] as any + break + case 'vector2': + this.default = [0, 0] as any + break + case 'vector4': + this.default = [0, 0, 0, 0] as any + break } } switch (this.type) { - case 'string': this.isString = true; break; - case 'enum': this.isEnum = true; break; - case 'molang': this.isMolang = true; break; - case 'number': this.isNumber = true; break; - case 'boolean': this.isBoolean = true; break; - case 'array': this.isArray = true; break; - case 'object': this.isObject = true; break; - case 'instance': this.isInstance = true; break; + case 'string': + this.isString = true + break + case 'enum': + this.isEnum = true + break + case 'molang': + this.isMolang = true + break + case 'number': + this.isNumber = true + break + case 'boolean': + this.isBoolean = true + break + case 'array': + this.isArray = true + break + case 'object': + this.isObject = true + break + case 'instance': + this.isInstance = true + break case 'vector': case 'vector2': - case 'vector4': this.isVector = true; break; + case 'vector4': + this.isVector = true + break } if (this.isMolang) { Object.defineProperty(target_class.prototype, `${name}_string`, { get() { - return typeof this[name] == 'number' ? trimFloatNumber(this[name]) || '0' : this[name]; + return typeof this[name] == 'number' + ? trimFloatNumber(this[name]) || '0' + : this[name] }, set(val) { - this[name] = val; - } + this[name] = val + }, }) } if (this.isEnum) { - this.enum_values = options.values; + this.enum_values = options.values } - if (typeof options.merge == 'function') this.merge = options.merge; - if (typeof options.reset == 'function') this.reset = options.reset; - if (typeof options.merge_validation == 'function') this.merge_validation = options.merge_validation; - if (options.condition) this.condition = options.condition; - if (options.exposed == false) this.exposed = false; - if (options.export == false) this.export = false; - if (options.copy_value == false) this.copy_value = false; - if (options.label) this.label = options.label; - if (options.description) this.description = options.description; - if (options.placeholder) this.placeholder = options.placeholder; - if (options.inputs) this.inputs = options.inputs; - if (options.options) this.options = options.options; + if (typeof options.merge == 'function') this.merge = options.merge + if (typeof options.reset == 'function') this.reset = options.reset + if (typeof options.merge_validation == 'function') + this.merge_validation = options.merge_validation + if (options.condition) this.condition = options.condition + if (options.exposed == false) this.exposed = false + if (options.export == false) this.export = false + if (options.copy_value == false) this.copy_value = false + if (options.label) this.label = options.label + if (options.description) this.description = options.description + if (options.placeholder) this.placeholder = options.placeholder + if (options.inputs) this.inputs = options.inputs + if (options.options) this.options = options.options } delete() { - delete this.class.properties[this.name]; + delete this.class.properties[this.name] } getDefault(instance: IPropertyType[T]): IPropertyType[T] { if (typeof this.default == 'function') { - return this.default(instance); + return this.default(instance) } else if (this.isArray) { - return this.default ? this.default.slice() : []; + return this.default ? this.default.slice() : [] } else if (this.isObject) { - return Object.keys(this.default).length ? structuredClone(this.default) : {} as any; + return Object.keys(this.default).length ? structuredClone(this.default) : ({} as any) } else { - return this.default; + return this.default } } merge(instance: IPropertyType[T], data: IPropertyType[T]): void { - if (data[this.name] == undefined || !Condition(this.condition, instance)) return; + if (data[this.name] == undefined || !Condition(this.condition, instance)) return if (this.isString) { Merge.string(instance, data, this.name, this.merge_validation) - } - else if (this.isEnum) { - Merge.string(instance, data, this.name, val => (!this.enum_values || this.enum_values.includes(val))); - } - else if (this.isNumber) { + } else if (this.isEnum) { + Merge.string( + instance, + data, + this.name, + val => !this.enum_values || this.enum_values.includes(val) + ) + } else if (this.isNumber) { Merge.number(instance, data, this.name) - } - else if (this.isMolang) { + } else if (this.isMolang) { Merge.molang(instance, data, this.name) - } - else if (this.isBoolean) { + } else if (this.isBoolean) { Merge.boolean(instance, data, this.name, this.merge_validation) - } - else if (this.isArray || this.isVector) { + } else if (this.isArray || this.isVector) { if (data[this.name] instanceof Array) { if (instance[this.name] instanceof Array == false) { - instance[this.name] = []; + instance[this.name] = [] } - instance[this.name].replace(data[this.name]); + instance[this.name].replace(data[this.name]) } - } - else if (this.isObject) { + } else if (this.isObject) { if (typeof data[this.name] == 'object') { - instance[this.name] = structuredClone(data[this.name]); + instance[this.name] = structuredClone(data[this.name]) } - } - else if (this.isInstance) { + } else if (this.isInstance) { if (typeof data[this.name] === 'object') { - instance[this.name] = data[this.name]; + instance[this.name] = data[this.name] } } } copy(instance: IPropertyType[T], target: IPropertyType[T]): void { - if (!Condition(this.condition, instance)) return; + if (!Condition(this.condition, instance)) return if (this.isArray || this.isVector) { if (instance[this.name] instanceof Array) { - target[this.name] = instance[this.name].slice(); + target[this.name] = instance[this.name].slice() if (this.isArray) { try { instance[this.name].forEach((item, i) => { if (typeof item == 'object') { - instance[this.name][i] = JSON.parse(JSON.stringify(item)); + instance[this.name][i] = JSON.parse(JSON.stringify(item)) } }) } catch (err) { - console.error(err); + console.error(err) } } } } else if (this.isObject) { if (typeof instance[this.name] == 'object') { - target[this.name] = structuredClone(instance[this.name]); + target[this.name] = structuredClone(instance[this.name]) } } else { - target[this.name] = instance[this.name]; + target[this.name] = instance[this.name] } } reset(instance: IPropertyType[T], force: boolean = false): void { - if (instance[this.name] == undefined && !Condition(this.condition, instance) && !force) return; + if (instance[this.name] == undefined && !Condition(this.condition, instance) && !force) + return var dft = this.getDefault(instance) if (this.isArray || this.isVector) { if (instance[this.name] instanceof Array == false) { - instance[this.name] = []; + instance[this.name] = [] } - instance[this.name].replace(dft || []); + instance[this.name].replace(dft || []) } else { - instance[this.name] = dft; + instance[this.name] = dft } } - static resetUniqueValues = function(type: any, instance: any) { + static resetUniqueValues = function (type: any, instance: any) { for (var key in type.properties) { - let property = type.properties[key]; + let property = type.properties[key] if (property.copy_value == false) { - property.reset(instance); + property.reset(instance) } } } } -Object.assign(window, {Property}); +Object.assign(window, { Property }) diff --git a/js/util/scoped_fs.ts b/js/util/scoped_fs.ts index e62e247ba..a5b5d855f 100644 --- a/js/util/scoped_fs.ts +++ b/js/util/scoped_fs.ts @@ -1,164 +1,164 @@ -const fs: typeof import("node:fs") = require('node:fs'); -const PathModule: typeof import("node:path") = require('node:path'); +const fs: typeof import('node:fs') = require('node:fs') +const PathModule: typeof import('node:path') = require('node:path') /** * @internal */ export function createScopedFS(scope?: string) { - const scope_path = scope ? PathModule.resolve(scope) : ''; + const scope_path = scope ? PathModule.resolve(scope) : '' function checkPath(path: string) { - path = PathModule.resolve(path); + path = PathModule.resolve(path) if (path.startsWith(scope_path) == false) { - throw `Trying to access path "${path}" outside of scoped file system "${scope_path}"`; + throw `Trying to access path "${path}" outside of scoped file system "${scope_path}"` } } return { scope: scope_path, copyFile(src: string, dest: string, mode, callback) { - checkPath(src); - checkPath(dest); - return fs.copyFile(src, dest, mode, callback); + checkPath(src) + checkPath(dest) + return fs.copyFile(src, dest, mode, callback) }, copyFileSync(src: string, dest: string, mode) { - checkPath(src); - checkPath(dest); - return fs.copyFileSync(src, dest, mode); + checkPath(src) + checkPath(dest) + return fs.copyFileSync(src, dest, mode) }, readFile(path: string, options, callback) { - checkPath(path); - return fs.readFile(path, options, callback); + checkPath(path) + return fs.readFile(path, options, callback) }, readFileSync(path: string, options) { - checkPath(path); - return fs.readFileSync(path, options); + checkPath(path) + return fs.readFileSync(path, options) }, writeFile(path: string, content, options, callback) { - checkPath(path); - return fs.writeFile(path, content, options, callback); + checkPath(path) + return fs.writeFile(path, content, options, callback) }, writeFileSync(path: string, content, options) { - checkPath(path); - return fs.writeFileSync(path, content, options); + checkPath(path) + return fs.writeFileSync(path, content, options) }, appendFile(path: string, content, options, callback) { - checkPath(path); - return fs.appendFile(path, content, options, callback); + checkPath(path) + return fs.appendFile(path, content, options, callback) }, appendFileSync(path: string, content, options) { - checkPath(path); - return fs.appendFileSync(path, content, options); + checkPath(path) + return fs.appendFileSync(path, content, options) }, existsSync(path) { - checkPath(path); - return fs.existsSync(path); + checkPath(path) + return fs.existsSync(path) }, mkdir(path: string, options, callback) { - checkPath(path); - return fs.mkdir(path, options, callback); + checkPath(path) + return fs.mkdir(path, options, callback) }, mkdirSync(path: string, options) { - checkPath(path); - return fs.mkdirSync(path, options); + checkPath(path) + return fs.mkdirSync(path, options) }, readdir(path: string, options, callback) { - checkPath(path); - return fs.readdir(path, options, callback); + checkPath(path) + return fs.readdir(path, options, callback) }, readdirSync(path: string, options) { - checkPath(path); - return fs.readdirSync(path, options); + checkPath(path) + return fs.readdirSync(path, options) }, rename(oldPath: string, newPath: string, callback) { - checkPath(oldPath); - checkPath(newPath); - return fs.rename(oldPath, newPath, callback); + checkPath(oldPath) + checkPath(newPath) + return fs.rename(oldPath, newPath, callback) }, renameSync(oldPath: string, newPath: string) { - checkPath(oldPath); - checkPath(newPath); - return fs.renameSync(oldPath, newPath); + checkPath(oldPath) + checkPath(newPath) + return fs.renameSync(oldPath, newPath) }, rm(path: string, options, callback) { - checkPath(path); - return fs.rm(path, options, callback); + checkPath(path) + return fs.rm(path, options, callback) }, rmSync(path: string, options) { - checkPath(path); - return fs.rm(path, options); + checkPath(path) + return fs.rm(path, options) }, rmdir(path: string, options, callback) { - checkPath(path); - return fs.rmdir(path, options, callback); + checkPath(path) + return fs.rmdir(path, options, callback) }, rmdirSync(path: string, options) { - checkPath(path); - return fs.rmdirSync(path, options); + checkPath(path) + return fs.rmdirSync(path, options) }, unlink(path: string, callback) { - checkPath(path); - return fs.unlink(path, callback); + checkPath(path) + return fs.unlink(path, callback) }, unlinkSync(path: string) { - checkPath(path); - return fs.unlinkSync(path); + checkPath(path) + return fs.unlinkSync(path) }, stat(path: string, callback) { - checkPath(path); - return fs.stat(path, callback); + checkPath(path) + return fs.stat(path, callback) }, statSync(path: string) { - checkPath(path); - return fs.statSync(path); + checkPath(path) + return fs.statSync(path) }, promises: { copyFile(src: string, dest: string, mode) { - checkPath(src); - checkPath(dest); - return fs.promises.copyFile(src, dest, mode); + checkPath(src) + checkPath(dest) + return fs.promises.copyFile(src, dest, mode) }, readFile(path: string, options) { - checkPath(path); - return fs.promises.readFile(path, options); + checkPath(path) + return fs.promises.readFile(path, options) }, writeFile(path: string, content, options) { - checkPath(path); - return fs.promises.writeFile(path, content, options); + checkPath(path) + return fs.promises.writeFile(path, content, options) }, appendFile(path: string, content, options) { - checkPath(path); - return fs.promises.appendFile(path, content, options); + checkPath(path) + return fs.promises.appendFile(path, content, options) }, mkdir(path: string, options) { - checkPath(path); - return fs.promises.mkdir(path, options); + checkPath(path) + return fs.promises.mkdir(path, options) }, readdir(path: string, options) { - checkPath(path); - return fs.promises.readdir(path, options); + checkPath(path) + return fs.promises.readdir(path, options) }, rename(oldPath: string, newPath: string) { - checkPath(oldPath); - checkPath(newPath); - return fs.promises.rename(oldPath, newPath); + checkPath(oldPath) + checkPath(newPath) + return fs.promises.rename(oldPath, newPath) }, rm(path: string, options) { - checkPath(path); - return fs.promises.rm(path, options); + checkPath(path) + return fs.promises.rm(path, options) }, rmdir(path: string, options) { - checkPath(path); - return fs.promises.rmdir(path, options); + checkPath(path) + return fs.promises.rmdir(path, options) }, unlink(path: string) { - checkPath(path); - return fs.promises.unlink(path); + checkPath(path) + return fs.promises.unlink(path) }, stat(path: string) { - checkPath(path); - return fs.promises.stat(path); - } - } + checkPath(path) + return fs.promises.stat(path) + }, + }, } } @@ -220,4 +220,4 @@ export function createScopedFS(scope?: string) { //write: write(fd, buffer, offsetOrOptions, length, position, callback) //writeSync: writeSync(fd, buffer, offsetOrOptions, length, position) //writev: writev(fd, buffers, position, callback) -//writevSync: writevSync(fd, buffers, position) \ No newline at end of file +//writevSync: writevSync(fd, buffers, position) diff --git a/js/util/state_memory.ts b/js/util/state_memory.ts index 5cf92b49c..09b41feee 100644 --- a/js/util/state_memory.ts +++ b/js/util/state_memory.ts @@ -2,45 +2,60 @@ const StateMemory = { /** * Initialize a memorized property */ - init(key: string, type: 'string'|'number'|'boolean'|'object'|'array') { + init(key: string, type: 'string' | 'number' | 'boolean' | 'object' | 'array') { let saved: any = localStorage.getItem(`StateMemory.${key}`) if (typeof saved == 'string') { try { saved = JSON.parse(saved) } catch (err) { - localStorage.removeItem(`StateMemory.${key}`); + localStorage.removeItem(`StateMemory.${key}`) } } - if ( saved !== null && (typeof saved == type || (type == 'array' && (saved instanceof Array))) ) { - StateMemory[key] = saved; + if ( + saved !== null && + (typeof saved == type || (type == 'array' && saved instanceof Array)) + ) { + StateMemory[key] = saved } else { - StateMemory[key] = (() => {switch (type) { - case 'string': return ''; break; - case 'number': return 0; break; - case 'boolean': return false; break; - case 'object': return {}; break; - case 'array': return []; break; - }})(); + StateMemory[key] = (() => { + switch (type) { + case 'string': + return '' + break + case 'number': + return 0 + break + case 'boolean': + return false + break + case 'object': + return {} + break + case 'array': + return [] + break + } + })() } }, set(key: string, value) { if (StateMemory[key] instanceof Array) { - StateMemory[key].replace(value); + StateMemory[key].replace(value) } else { - StateMemory[key] = value; + StateMemory[key] = value } - StateMemory.save(key); + StateMemory.save(key) }, save(key: string) { let serialized = JSON.stringify(StateMemory[key]) localStorage.setItem(`StateMemory.${key}`, serialized) }, - get(key: string): string|number|[]|boolean|any { - return StateMemory[key]; - } + get(key: string): string | number | [] | boolean | any { + return StateMemory[key] + }, } -export default StateMemory; +export default StateMemory Object.assign(window, { StateMemory, -}); +}) diff --git a/js/util/yaml.ts b/js/util/yaml.ts index 4be31f039..8a53098aa 100644 --- a/js/util/yaml.ts +++ b/js/util/yaml.ts @@ -4,58 +4,60 @@ export namespace BBYaml { function parseValue(value: string): any { switch (value) { - case 'true': return true; - case 'false': return false; + case 'true': + return true + case 'false': + return false } if (value.startsWith('"') && value.endsWith('"')) { - return value.substring(1, value.length-1); + return value.substring(1, value.length - 1) } // @ts-ignore if (!isNaN(value)) { - return parseFloat(value); + return parseFloat(value) } - return value; + return value } export function parse(input: string): any { - let lines = input.split(/(\r?\n)+/g); - let root = {}; - let stack: (any)[] = [root]; - let last_key: string; - + let lines = input.split(/(\r?\n)+/g) + let root = {} + let stack: any[] = [root] + let last_key: string + for (let line of lines) { - let indent_level = line.match(/^\s*/)[0]?.replace(/ /g, '\t').length; - let [key, value] = line.split(/: *(.*)/s); - key = key.trim(); - if (!key || key.startsWith('#')) continue; - while (indent_level < stack.length-1) { - stack.pop(); + let indent_level = line.match(/^\s*/)[0]?.replace(/ /g, '\t').length + let [key, value] = line.split(/: *(.*)/s) + key = key.trim() + if (!key || key.startsWith('#')) continue + while (indent_level < stack.length - 1) { + stack.pop() } if (key.startsWith('- ')) { - key = key.substring(2); - if (stack.last() instanceof Array == false && stack[stack.length-2]) { + key = key.substring(2) + if (stack.last() instanceof Array == false && stack[stack.length - 2]) { // Convert to array - stack[stack.length-2][last_key] = stack[stack.length-1] = []; + stack[stack.length - 2][last_key] = stack[stack.length - 1] = [] } - + if (typeof value == 'string') { - let obj = {}; - stack.last().push(obj); - stack.push(obj); + let obj = {} + stack.last().push(obj) + stack.push(obj) } else { - stack.last().push(parseValue(key)); - continue; + stack.last().push(parseValue(key)) + continue } } if (typeof value == 'string') { if (value) { - stack.last()[key] = parseValue(value); + stack.last()[key] = parseValue(value) } else { - let obj = stack.last()[key] = {}; - stack.push(obj); + let obj = (stack.last()[key] = {}) + stack.push(obj) } } - last_key = key; + last_key = key } - return root; + return root } } From d0276d236ae01960ac46f3c0e0e5f58dd6f64142 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 11 Sep 2025 13:37:10 -0400 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A7=20Prettier:=20Enable=20Semi-Co?= =?UTF-8?q?lons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierrc.json b/.prettierrc.json index f073eda2c..fd5ef5439 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "$schema": "http://json.schemastore.org/prettierrc", "tabWidth": 4, "useTabs": true, - "semi": false, + "semi": true, "singleQuote": true, "arrowParens": "avoid", "printWidth": 100, From f250710336c08156a2d9cf3d5f30dc9ab3d12b73 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 11 Sep 2025 13:37:36 -0400 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=8E=A8=20Update=20File=20Formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/animations/molang_editor.ts | 48 +- js/api.ts | 321 +++--- js/display_mode/DisplayModePanel.vue | 105 +- js/display_mode/display_references.ts | 6 +- js/file_system.ts | 513 ++++----- js/global_types.ts | 54 +- js/interface/dialog.ts | 1184 ++++++++++----------- js/interface/form.ts | 977 ++++++++--------- js/interface/panels.ts | 1270 +++++++++++----------- js/interface/settings.ts | 499 ++++----- js/interface/settings_window.ts | 196 ++-- js/interface/themes.ts | 750 ++++++------- js/io/format.ts | 582 +++++----- js/io/formats/fbx.ts | 1155 ++++++++++---------- js/io/formats/generic.ts | 2 +- js/io/formats/skin.ts | 764 +++++++------- js/io/share.ts | 166 +-- js/languages.ts | 86 +- js/lib/CanvasFrame.ts | 108 +- js/lib/libs.ts | 38 +- js/main.ts | 236 ++--- js/modeling/edit.ts | 16 +- js/modeling/mesh/attach_armature.ts | 288 ++--- js/modeling/mesh/proportional_edit.ts | 110 +- js/modeling/mesh/set_vertex_weights.ts | 40 +- js/modeling/mesh/util.ts | 2 +- js/modeling/mirror_modeling.ts | 616 +++++------ js/modeling/weight_paint.ts | 218 ++-- js/modes.ts | 240 ++--- js/native_apis.ts | 156 +-- js/native_apis_web.ts | 6 +- js/outliner/armature.ts | 246 ++--- js/outliner/armature_bone.ts | 524 ++++----- js/outliner/collections.ts | 590 +++++------ js/outliner/element_panel.ts | 108 +- js/plugin_loader.ts | 1345 ++++++++++++------------ js/shaders/shader.ts | 8 +- js/texturing/ColorPickerNormal.vue | 46 +- js/util/event_system.ts | 60 +- js/util/global.d.ts | 126 +-- js/util/json.ts | 170 +-- js/util/molang.ts | 64 +- js/util/property.ts | 294 +++--- js/util/scoped_fs.ts | 160 +-- js/util/state_memory.ts | 48 +- js/util/yaml.ts | 52 +- types/custom/animation.d.ts | 17 +- types/custom/blockbench.d.ts | 14 +- types/custom/display_mode.d.ts | 115 +- types/custom/display_references.d.ts | 41 + types/custom/project.d.ts | 17 +- 51 files changed, 7445 insertions(+), 7352 deletions(-) create mode 100644 types/custom/display_references.d.ts diff --git a/js/animations/molang_editor.ts b/js/animations/molang_editor.ts index e92d45d70..c17bf2ec9 100644 --- a/js/animations/molang_editor.ts +++ b/js/animations/molang_editor.ts @@ -1,12 +1,12 @@ -import { MolangAutocomplete } from './molang' +import { MolangAutocomplete } from './molang'; interface MolangEditorOptions { - autocomplete_context: MolangAutocomplete.Context - text: string + autocomplete_context: MolangAutocomplete.Context; + text: string; } export function openMolangEditor(options: MolangEditorOptions, callback: (result: string) => void) { interface VueData { - text: string + text: string; } let dialog = new Dialog('expression_editor', { title: 'menu.text_edit.expression_editor', @@ -19,14 +19,14 @@ export function openMolangEditor(options: MolangEditorOptions, callback: (result }, methods: { prettyPrint(this: VueData) { - this.text = this.text.replace(/;\s*(?!\n)/g, ';\n') + this.text = this.text.replace(/;\s*(?!\n)/g, ';\n'); }, minify(this: VueData) { - this.text = this.text.replace(/\n/g, '').replace(/\s{2,}/g, ' ') + this.text = this.text.replace(/\n/g, '').replace(/\s{2,}/g, ' '); }, findReplace(this: VueData) { - this - let scope = this + this; + let scope = this; new Dialog({ id: 'find_replace', title: 'action.find_replace', @@ -40,23 +40,23 @@ export function openMolangEditor(options: MolangEditorOptions, callback: (result }, }, onConfirm(form) { - if (!form.find) return + if (!form.find) return; function replace(text: string) { if (form.regex) { - let regex = new RegExp(form.find, 'g') - return text.replace(regex, form.replace) + let regex = new RegExp(form.find, 'g'); + return text.replace(regex, form.replace); } else { - return text.split(form.find).join(form.replace) + return text.split(form.find).join(form.replace); } } - scope.text = replace(scope.text) + scope.text = replace(scope.text); }, - }).show() + }).show(); }, autocomplete(text: string, position: number) { - if (Settings.get('autocomplete_code') == false) return [] - let test = options.autocomplete_context.autocomplete(text, position) - return test + if (Settings.get('autocomplete_code') == false) return []; + let test = options.autocomplete_context.autocomplete(text, position); + return test; }, }, template: ` @@ -81,18 +81,18 @@ export function openMolangEditor(options: MolangEditorOptions, callback: (result onOpen() { let element = document.querySelector( '#expression_editor_prism.molang_input' - ) as HTMLElement - element.style.height = dialog.object.clientHeight - 50 + 'px' + ) as HTMLElement; + element.style.height = dialog.object.clientHeight - 50 + 'px'; }, onResize() { let element = document.querySelector( '#expression_editor_prism.molang_input' - ) as HTMLElement - element.style.height = dialog.object.clientHeight - 50 + 'px' + ) as HTMLElement; + element.style.height = dialog.object.clientHeight - 50 + 'px'; }, onConfirm() { - callback(dialog.content_vue.$data.text) + callback(dialog.content_vue.$data.text); }, - }) - dialog.show() + }); + dialog.show(); } diff --git a/js/api.ts b/js/api.ts index 3ceb5c60d..610b76ad5 100644 --- a/js/api.ts +++ b/js/api.ts @@ -1,40 +1,40 @@ -import { FormElementOptions } from './interface/form' -import { ModelFormat } from './io/format' -import { Prop } from './misc' -import { EventSystem } from './util/event_system' -import { compareVersions } from './util/util' -import { Filesystem } from './file_system' -import { MessageBoxOptions } from './interface/dialog' -import { currentwindow, shell, SystemInfo } from './native_apis' +import { FormElementOptions } from './interface/form'; +import { ModelFormat } from './io/format'; +import { Prop } from './misc'; +import { EventSystem } from './util/event_system'; +import { compareVersions } from './util/util'; +import { Filesystem } from './file_system'; +import { MessageBoxOptions } from './interface/dialog'; +import { currentwindow, shell, SystemInfo } from './native_apis'; -declare const appVersion: string -declare let Format: ModelFormat +declare const appVersion: string; +declare let Format: ModelFormat; interface ToastNotificationOptions { /** * Text message */ - text: string + text: string; /** * Blockbench icon string */ - icon?: string + icon?: string; /** * Expire time in miliseconds */ - expire?: number + expire?: number; /** * Background color, accepts any CSS color string */ - color?: string + color?: string; /** * Method to run on click. * @returns Return `true` to close toast */ - click?: (event: Event) => boolean + click?: (event: Event) => boolean; } export const LastVersion = - localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion + localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion; export const Blockbench = { ...window.Blockbench, @@ -43,7 +43,7 @@ export const Blockbench = { isLandscape: window.innerWidth > window.innerHeight, isTouch: 'ontouchend' in document, get isPWA() { - return 'standalone' in navigator || window.matchMedia('(display-mode: standalone)').matches + return 'standalone' in navigator || window.matchMedia('(display-mode: standalone)').matches; }, version: appVersion, operating_system: '', @@ -57,135 +57,135 @@ export const Blockbench = { * @deprecated Use Undo.initEdit and Undo.finishEdit instead */ edit(aspects: UndoAspects, cb: () => void) { - Undo.initEdit(aspects) - cb() - Undo.finishEdit('Edit') + Undo.initEdit(aspects); + cb(); + Undo.finishEdit('Edit'); }, reload() { if (isApp) { - Blockbench.setProgress(0) - Blockbench.addFlag('allow_closing') - Blockbench.addFlag('allow_reload') - location.reload() + Blockbench.setProgress(0); + Blockbench.addFlag('allow_closing'); + Blockbench.addFlag('allow_reload'); + location.reload(); } else { - location.reload() + location.reload(); } }, isNewerThan(version: string): boolean { - return compareVersions(Blockbench.version, version) + return compareVersions(Blockbench.version, version); }, isOlderThan(version: string): boolean { - return compareVersions(version, Blockbench.version) + return compareVersions(version, Blockbench.version); }, registerEdit() { console.warn( 'Blockbench.registerEdit is outdated. Please use Undo.initEdit and Undo.finishEdit' - ) + ); }, //Interface getIconNode( icon: IconString | boolean | HTMLElement | (() => IconString | boolean | HTMLElement), color?: string ) { - let node + let node; if (typeof icon === 'function') { - icon = icon() + icon = icon(); } if (icon === undefined) { //Missing - node = document.createElement('i') - node.classList.add('material-icons', 'notranslate', 'icon') - node.innerText = 'help_outline' + node = document.createElement('i'); + node.classList.add('material-icons', 'notranslate', 'icon'); + node.innerText = 'help_outline'; } else if (icon instanceof HTMLElement) { //Node - node = icon + node = icon; } else if (icon === true || icon === false) { //Boolean - node = document.createElement('i') - node.classList.add('material-icons', 'notranslate', 'icon') - node.innerText = icon ? 'check_box' : 'check_box_outline_blank' + node = document.createElement('i'); + node.classList.add('material-icons', 'notranslate', 'icon'); + node.innerText = icon ? 'check_box' : 'check_box_outline_blank'; } else if (icon === null) { //Node - node = document.createElement('i') - node.classList.add('fa_big', 'icon') + node = document.createElement('i'); + node.classList.add('fa_big', 'icon'); } else if (icon.match(/^(fa[.-])|(fa[rsb]\.)/)) { //Font Awesome - node = document.createElement('i') - node.classList.add('fa_big', 'icon') + node = document.createElement('i'); + node.classList.add('fa_big', 'icon'); if (icon.substr(3, 1) === '.') { - node.classList.add(icon.substr(0, 3), icon.substr(4)) + node.classList.add(icon.substr(0, 3), icon.substr(4)); } else { - node.classList.add('fa', icon) + node.classList.add('fa', icon); } } else if (icon.substr(0, 5) === 'icon-') { //Icomoon - node = document.createElement('i') - node.classList.add(icon, 'icon') + node = document.createElement('i'); + node.classList.add(icon, 'icon'); } else if (icon.substr(0, 14) === 'data:image/png') { //Data URL - node = document.createElement('img') - node.classList.add('icon') - node.src = icon + node = document.createElement('img'); + node.classList.add('icon'); + node.src = icon; } else { //Material Icon - node = document.createElement('i') - node.classList.add('material-icons', 'notranslate', 'icon') - node.innerText = icon + node = document.createElement('i'); + node.classList.add('material-icons', 'notranslate', 'icon'); + node.innerText = icon; } if (color) { if (color === 'x') { - node.classList.add('color_x') + node.classList.add('color_x'); } else if (color === 'y') { - node.classList.add('color_y') + node.classList.add('color_y'); } else if (color === 'z') { - node.classList.add('color_z') + node.classList.add('color_z'); } else if (color === 'u') { - node.classList.add('color_u') + node.classList.add('color_u'); } else if (color === 'v') { - node.classList.add('color_v') + node.classList.add('color_v'); } else if (color === 'w') { - node.classList.add('color_w') + node.classList.add('color_w'); } else if (typeof color === 'string') { - node.style.color = color + node.style.color = color; } } - return node + return node; }, showQuickMessage(message, time = 1000) { - document.getElementById('quick_message_box')?.remove() + document.getElementById('quick_message_box')?.remove(); let quick_message_box = Interface.createElement( 'div', { id: 'quick_message_box' }, tl(message) - ) - document.body.append(quick_message_box) + ); + document.body.append(quick_message_box); setTimeout(function () { - quick_message_box.remove() - }, time) + quick_message_box.remove(); + }, time); }, showToastNotification(options: ToastNotificationOptions) { - let notification = document.createElement('li') - notification.className = 'toast_notification' + let notification = document.createElement('li'); + notification.className = 'toast_notification'; if (options.icon) { - let icon = Blockbench.getIconNode(options.icon) - notification.append(icon) + let icon = Blockbench.getIconNode(options.icon); + notification.append(icon); } - let text = document.createElement('span') - text.innerText = tl(options.text) - notification.append(text) + let text = document.createElement('span'); + text.innerText = tl(options.text); + notification.append(text); - let close_button = document.createElement('div') - close_button.innerHTML = 'clear' - close_button.className = 'toast_close_button' + let close_button = document.createElement('div'); + close_button.innerHTML = 'clear'; + close_button.className = 'toast_close_button'; close_button.addEventListener('click', event => { - notification.remove() - }) - notification.append(close_button) + notification.remove(); + }); + notification.append(close_button); if (options.color) { - notification.style.backgroundColor = options.color + notification.style.backgroundColor = options.color; } if (typeof options.click == 'function') { notification.addEventListener('click', event => { @@ -193,57 +193,57 @@ export const Blockbench = { event.target == close_button || (event.target as HTMLElement).parentElement == close_button ) - return - let result = options.click(event) + return; + let result = options.click(event); if (result == true) { - notification.remove() + notification.remove(); } - }) - notification.style.cursor = 'pointer' + }); + notification.style.cursor = 'pointer'; } if (options.expire) { setTimeout(() => { - notification.remove() - }, options.expire) + notification.remove(); + }, options.expire); } - document.getElementById('toast_notification_list').append(notification) + document.getElementById('toast_notification_list').append(notification); function deletableToast(node: HTMLElement) { this.delete = function () { - node.remove() - } + node.remove(); + }; } - return new deletableToast(notification) + return new deletableToast(notification); }, setCursorTooltip(text?: string): void {}, setProgress(progress: number, time: number = 0, bar?: string): void {}, showStatusMessage(message: string, time: number = 800) { - Blockbench.setStatusBarText(tl(message)) + Blockbench.setStatusBarText(tl(message)); setTimeout(function () { - Blockbench.setStatusBarText() - }, time) + Blockbench.setStatusBarText(); + }, time); }, setStatusBarText(text?: string) { if (text !== undefined) { - Prop.file_name = text + Prop.file_name = text; } else { - Prop.file_name = Prop.file_name_alt || '' + Prop.file_name = Prop.file_name_alt || ''; } }, showMessage(message, location) { if (location === 'status_bar') { - Blockbench.showStatusMessage(message) + Blockbench.showStatusMessage(message); } else if (location === 'center') { - Blockbench.showQuickMessage(message) + Blockbench.showQuickMessage(message); } }, showMessageBox( options: MessageBoxOptions, cb?: (button: number | string, result?: Record, event?: Event) => void ) { - return new MessageBox(options, cb).show() + return new MessageBox(options, cb).show(); }, /** * @@ -262,8 +262,8 @@ export const Blockbench = { options: { placeholder?: string; description?: string; info?: string } = {} ) { if (typeof options == 'string') { - options = { placeholder: options } - console.warn('textPrompt: 4th argument is expected to be an object') + options = { placeholder: options }; + console.warn('textPrompt: 4th argument is expected to be an object'); } let answer = await new Promise(resolve => { let form: Record = { @@ -274,112 +274,112 @@ export const Blockbench = { value, description: options.description, }, - } + }; if (options.info) { form.description = { type: 'info', text: tl(options.info), - } + }; } new Dialog({ id: 'text_input', title: title || 'dialog.input.title', form, onConfirm({ text }) { - if (callback) callback(text) - resolve(text) + if (callback) callback(text); + resolve(text); }, onOpen() { - this.object.querySelector('input')?.focus() + this.object.querySelector('input')?.focus(); }, - }).show() - }) - return answer + }).show(); + }); + return answer; }, addMenuEntry(name: string, icon: IconString, click) { - console.warn('Blockbench.addMenuEntry is deprecated. Please use Actions instead.') - let id = name.replace(/\s/g, '').toLowerCase() - var action = new Action(id, { icon: icon, name: name, click: click }) - MenuBar.addAction(action, 'tools') + console.warn('Blockbench.addMenuEntry is deprecated. Please use Actions instead.'); + let id = name.replace(/\s/g, '').toLowerCase(); + var action = new Action(id, { icon: icon, name: name, click: click }); + MenuBar.addAction(action, 'tools'); }, removeMenuEntry(name: string) { - let id = name.replace(/\s/g, '').toLowerCase() - MenuBar.removeAction('tools.' + id) + let id = name.replace(/\s/g, '').toLowerCase(); + MenuBar.removeAction('tools.' + id); }, openLink(link: string) { if (isApp) { - shell.openExternal(link) + shell.openExternal(link); } else { - window.open(link) + window.open(link); } }, notification(title: string, text: string, icon?: string) { Notification.requestPermission().then(status => { if (status == 'granted') { - let n = new Notification(title, { body: text, icon: icon || 'favicon.png' }) + let n = new Notification(title, { body: text, icon: icon || 'favicon.png' }); n.onclick = function () { if (isApp) { // @ts-ignore - currentwindow.focus() + currentwindow.focus(); } else { - window.focus() + window.focus(); } - } + }; } - }) + }); }, //CSS addCSS(css: string): Deletable { - let style_node = document.createElement('style') - style_node.type = 'text/css' - style_node.appendChild(document.createTextNode(css)) - document.getElementsByTagName('head')[0].appendChild(style_node) + let style_node = document.createElement('style'); + style_node.type = 'text/css'; + style_node.appendChild(document.createTextNode(css)); + document.getElementsByTagName('head')[0].appendChild(style_node); function deletableStyle(node) { this.delete = function () { - node.remove() - } + node.remove(); + }; } - return new deletableStyle(style_node) + return new deletableStyle(style_node); }, //Flags addFlag(flag: string): void { - this.flags[flag] = true + this.flags[flag] = true; }, removeFlag(flag: string): void { - delete this.flags[flag] + delete this.flags[flag]; }, hasFlag(flag: string): boolean | undefined { - return this.flags[flag] + return this.flags[flag]; }, //Events dispatchEvent(event_name: EventName, data: any): any[] { - let list = this.events[event_name] - let results: any[] + let list = this.events[event_name]; + let results: any[]; if (list) { - results = [] + results = []; for (let i = 0; i < list.length; i++) { if (typeof list[i] === 'function') { - let result = list[i](data) - results.push(result) + let result = list[i](data); + results.push(result); } } } if (Validator.triggers.includes(event_name)) { - Validator.validate(event_name) + Validator.validate(event_name); } - return results + return results; }, on(event_name: EventName, cb) { - return EventSystem.prototype.on.call(this, event_name, cb) + return EventSystem.prototype.on.call(this, event_name, cb); }, once(event_name: EventName, cb) { - return EventSystem.prototype.once.call(this, event_name, cb) + return EventSystem.prototype.once.call(this, event_name, cb); }, addListener(event_name: EventName, cb) { - return EventSystem.prototype.addListener.call(this, event_name, cb) + return EventSystem.prototype.addListener.call(this, event_name, cb); }, removeListener(event_name: EventName, cb) { - return EventSystem.prototype.removeListener.call(this, event_name, cb) + return EventSystem.prototype.removeListener.call(this, event_name, cb); }, // Update onUpdateTo(version, callback) { @@ -388,14 +388,14 @@ export const Blockbench = { compareVersions(version, LastVersion) && !Blockbench.isOlderThan(version) ) { - callback(LastVersion) + callback(LastVersion); } }, // Globals Format: 0 as ModelFormat | number, Project: 0 as ModelProject | number, get Undo() { - return Project?.undo + return Project?.undo; }, // File System import: Filesystem.importFile, @@ -409,40 +409,39 @@ export const Blockbench = { findFileFromContent: Filesystem.findFileFromContent, addDragHandler: Filesystem.addDragHandler, removeDragHandler: Filesystem.removeDragHandler, -} - -;(function () { +}; +(function () { if (!LastVersion || LastVersion.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) { - Blockbench.addFlag('after_update') + Blockbench.addFlag('after_update'); } else if (LastVersion != appVersion) { - Blockbench.addFlag('after_patch_update') + Blockbench.addFlag('after_patch_update'); } try { - let ui_mode = JSON.parse(localStorage.getItem('settings')).interface_mode.value - if (ui_mode == 'desktop') Blockbench.isMobile = false - if (ui_mode == 'mobile') Blockbench.isMobile = true + let ui_mode = JSON.parse(localStorage.getItem('settings')).interface_mode.value; + if (ui_mode == 'desktop') Blockbench.isMobile = false; + if (ui_mode == 'mobile') Blockbench.isMobile = true; } catch (err) {} -})() +})(); if (isApp) { - Blockbench.platform = SystemInfo.platform + Blockbench.platform = SystemInfo.platform; switch (Blockbench.platform) { case 'win32': - Blockbench.operating_system = 'Windows' - break + Blockbench.operating_system = 'Windows'; + break; case 'darwin': - Blockbench.operating_system = 'macOS' - break + Blockbench.operating_system = 'macOS'; + break; default: - Blockbench.operating_system = 'Linux' - break + Blockbench.operating_system = 'Linux'; + break; } // @ts-ignore - if (Blockbench.platform.includes('win32') === true) window.osfs = '\\' + if (Blockbench.platform.includes('win32') === true) window.osfs = '\\'; } Object.assign(window, { LastVersion, Blockbench, isApp, -}) +}); diff --git a/js/display_mode/DisplayModePanel.vue b/js/display_mode/DisplayModePanel.vue index 139aa2b86..592a13779 100644 --- a/js/display_mode/DisplayModePanel.vue +++ b/js/display_mode/DisplayModePanel.vue @@ -93,8 +93,8 @@ :step="0.5" @input="change(axis, 'rotation')" @change=" - focusout(axis, 'rotation') - save() + focusout(axis, 'rotation'); + save(); " @mousedown="start()" /> @@ -146,8 +146,8 @@ :step="0.5" @input="change(axis, 'translation')" @change=" - focusout(axis, 'translation') - save() + focusout(axis, 'translation'); + save(); " @mousedown="start()" /> @@ -205,8 +205,8 @@ :step="0.01" @input="change(axis, 'scale')" @change=" - focusout(axis, 'scale') - save() + focusout(axis, 'scale'); + save(); " @mousedown="start()" /> @@ -265,8 +265,8 @@ :step="0.05" @input="change(axis, 'rotation_pivot')" @change=" - focusout(axis, 'rotation_pivot') - save() + focusout(axis, 'rotation_pivot'); + save(); " @mousedown="start()" /> @@ -293,8 +293,8 @@ :step="0.05" @input="change(axis, 'scale_pivot')" @change=" - focusout(axis, 'scale_pivot') - save() + focusout(axis, 'scale_pivot'); + save(); " @mousedown="start()" /> @@ -305,8 +305,8 @@ diff --git a/js/display_mode/display_references.ts b/js/display_mode/display_references.ts index 53702a9d2..e4df5ddaa 100644 --- a/js/display_mode/display_references.ts +++ b/js/display_mode/display_references.ts @@ -1,4 +1,4 @@ -import { PreviewModel } from '../preview/preview_scenes' +import { PreviewModel } from '../preview/preview_scenes'; /** * @internal @@ -912,5 +912,5 @@ const DisplayReferences = { }, ], }, -} -export default DisplayReferences +}; +export default DisplayReferences; diff --git a/js/file_system.ts b/js/file_system.ts index 4e62882ac..7a916c84d 100644 --- a/js/file_system.ts +++ b/js/file_system.ts @@ -1,26 +1,26 @@ -import saveAs from 'file-saver' -import StateMemory from './util/state_memory' -import { pathToExtension } from './util/util' -import { app, currentwindow, electron, fs, ipcRenderer, webUtils } from './native_apis' +import saveAs from 'file-saver'; +import StateMemory from './util/state_memory'; +import { pathToExtension } from './util/util'; +import { app, currentwindow, electron, fs, ipcRenderer, webUtils } from './native_apis'; function isStreamerMode(): boolean { // @ts-ignore - return window.settings.streamer_mode.value + return window.settings.streamer_mode.value; } declare class Blockbench { - static isTouch: boolean - static showMessageBox(options: any): void - static showQuickMessage(message: string): void + static isTouch: boolean; + static showMessageBox(options: any): void; + static showQuickMessage(message: string): void; } export namespace Filesystem { export type FileResult = { - name: string - path: string - content?: string | ArrayBuffer - browser_file?: File - } + name: string; + path: string; + content?: string | ArrayBuffer; + browser_file?: File; + }; /** * The resource identifier group, used to allow the file dialog (open and save) to remember where it was last used @@ -39,36 +39,36 @@ export namespace Filesystem { | 'obj' | 'preview_background' | 'screenshot' - | 'palette' + | 'palette'; // MARK: Import - type ReadType = 'buffer' | 'binary' | 'text' | 'image' | 'none' + type ReadType = 'buffer' | 'binary' | 'text' | 'image' | 'none'; interface ReadOptions { - readtype?: ReadType | ((file: string) => ReadType) - errorbox?: boolean + readtype?: ReadType | ((file: string) => ReadType); + errorbox?: boolean; /** File Extensions */ - extensions?: string[] + extensions?: string[]; } interface ImportOptions extends ReadOptions { /** Name of the file type */ - type: string + type: string; /** File Extensions */ - extensions: string[] + extensions: string[]; /** Allow selection of multiple elements */ - multiple?: boolean + multiple?: boolean; /** File picker start path */ - startpath?: string + startpath?: string; /** The resource identifier group, used to allow the file dialog (open and save) to remember where it was last used */ - resource_id?: ResourceID + resource_id?: ResourceID; /** Title of the file picker window */ - title?: string + title?: string; /** */ } @@ -80,16 +80,16 @@ export namespace Filesystem { */ export function importFile(options: ImportOptions, callback?: (files: FileResult[]) => void) { if (isApp) { - let properties = [] + let properties = []; if (options.multiple) { - properties.push('openFile', 'multiSelections') + properties.push('openFile', 'multiSelections'); } if (options.extensions[0] === 'image/*') { - options.type = 'Images' - options.extensions = ['png', 'jpg', 'jpeg', 'bmp', 'tiff', 'tif', 'gif'] + options.type = 'Images'; + options.extensions = ['png', 'jpg', 'jpeg', 'bmp', 'tiff', 'tif', 'gif']; } if (!options.startpath && options.resource_id) { - options.startpath = StateMemory.get('dialog_paths')[options.resource_id] + options.startpath = StateMemory.get('dialog_paths')[options.resource_id]; } let fileNames = electron.dialog.showOpenDialogSync(currentwindow, { @@ -104,15 +104,15 @@ export namespace Filesystem { ? properties.concat(['dontAddToRecent']) : ['dontAddToRecent'], defaultPath: isStreamerMode() ? app.getPath('desktop') : options.startpath, - }) - if (!fileNames) return + }); + if (!fileNames) return; if (options.resource_id) { StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname( fileNames[0] - ) - StateMemory.save('dialog_paths') + ); + StateMemory.save('dialog_paths'); } - readFile(fileNames, options, callback) + readFile(fileNames, options, callback); } else { let isIOS = [ @@ -123,26 +123,26 @@ export namespace Filesystem { 'iPhone', 'iPod', ].includes(navigator.platform) || - (navigator.userAgent.includes('Mac') && 'ontouchend' in document) + (navigator.userAgent.includes('Mac') && 'ontouchend' in document); let element = Interface.createElement('input', { type: 'file', accept: '.' + (options.extensions ? options.extensions.join(',.') : ''), multiple: options.multiple === true ? 'true' : 'false', '@change'(event: Event) { - readFile(this.files, options, callback) + readFile(this.files, options, callback); }, - }) as HTMLInputElement + }) as HTMLInputElement; if ( (isIOS || Blockbench.isTouch) && options.extensions && options.extensions.length > 1 ) { - let ext_options = {} + let ext_options = {}; options.extensions.forEach(extension => { - ext_options[extension] = extension - }) + ext_options[extension] = extension; + }); new Dialog({ id: 'import_type', title: 'File Type', @@ -150,12 +150,12 @@ export namespace Filesystem { extension: { label: 'File Type', type: 'select', options: ext_options }, }, onConfirm(formResult) { - element.setAttribute('accept', '.' + formResult.extension) - $(element).trigger('click') + element.setAttribute('accept', '.' + formResult.extension); + $(element).trigger('click'); }, - }).show() + }).show(); } else { - $(element).trigger('click') + $(element).trigger('click'); } } } @@ -166,69 +166,69 @@ export namespace Filesystem { options: ReadOptions = {}, callback?: (files: FileResult[]) => void ) { - if (files == undefined) return false - if (typeof files == 'string') files = [files] + if (files == undefined) return false; + if (typeof files == 'string') files = [files]; - let results: FileResult[] = [] - let result_count = 0 - let errant = false + let results: FileResult[] = []; + let result_count = 0; + let errant = false; if (isApp && files instanceof FileList == false) { if (options.readtype == 'none') { let results = files.map(file => { return { name: pathToName(file, true), path: file, - } - }) - callback(results) - return results + }; + }); + callback(results); + return results; } files.forEach((file, i) => { - let readtype: ReadType + let readtype: ReadType; if (typeof options.readtype == 'function') { - readtype = options.readtype(file) + readtype = options.readtype(file); } else { - readtype = options.readtype + readtype = options.readtype; } - let binary = readtype === 'buffer' || readtype === 'binary' + let binary = readtype === 'buffer' || readtype === 'binary'; if (!readtype) { - readtype = 'text' + readtype = 'text'; } if (readtype === 'image') { // - let extension = pathToExtension(file) + let extension = pathToExtension(file); if (extension === 'tga') { - let targa_loader = new Targa() + let targa_loader = new Targa(); targa_loader.open(file, () => { results[i] = { name: pathToName(file, true), path: file, content: targa_loader.getDataURL(), - } + }; - result_count++ + result_count++; if (result_count === files.length) { - callback(results) + callback(results); } - }) + }); } else { results[i] = { name: pathToName(file, true), path: file, content: file, - } - result_count++ + }; + result_count++; if (result_count === files.length) { - callback(results) + callback(results); } } } /*text*/ else { - let data + let data; try { - data = fs.readFileSync(file, readtype == 'text' ? 'utf8' : undefined) + data = fs.readFileSync(file, readtype == 'text' ? 'utf8' : undefined); } catch (err) { - console.error(err) + console.error(err); if (!errant && options.errorbox !== false) { Blockbench.showMessageBox({ translateKey: 'file_not_found', @@ -239,64 +239,64 @@ export namespace Filesystem { '```', icon: 'error_outline', width: 520, - }) + }); } - errant = true - return + errant = true; + return; } if (binary) { - let ab = new ArrayBuffer(data.length) - let view = new Uint8Array(ab) + let ab = new ArrayBuffer(data.length); + let view = new Uint8Array(ab); for (let i = 0; i < data.length; ++i) { - view[i] = data[i] + view[i] = data[i]; } - data = ab + data = ab; } if (!binary && data.charCodeAt(0) === 0xfeff) { - data = data.substr(1) + data = data.substr(1); } results[i] = { name: pathToName(file, true), path: file, content: data, - } - result_count++ + }; + result_count++; if (result_count === files.length) { - callback(results) + callback(results); } } - }) + }); } else { - let i = 0 + let i = 0; for (let file of files as FileList) { - let reader = new FileReader() - let local_i = i + let reader = new FileReader(); + let local_i = i; reader.onloadend = function () { - let result + let result; if ( typeof reader.result != 'string' && reader.result.byteLength && pathToExtension(name) === 'tga' ) { - let arr = new Uint8Array(reader.result) - let targa_loader = new Targa() - targa_loader.load(arr) - result = targa_loader.getDataURL() + let arr = new Uint8Array(reader.result); + let targa_loader = new Targa(); + targa_loader.load(arr); + result = targa_loader.getDataURL(); } else { - result = reader.result + result = reader.result; } results[local_i] = { name, path: name, content: result, browser_file: file, - } - result_count++ + }; + result_count++; if (result_count === files.length) { - callback(results) + callback(results); } - } - let name = file.name + }; + let name = file.name; if ( pathToExtension(name) === 'txt' && !( @@ -305,40 +305,40 @@ export namespace Filesystem { options.extensions.includes('txt') ) ) { - name = name.replace(/\.txt$/i, '') + name = name.replace(/\.txt$/i, ''); } - let readtype = options.readtype + let readtype = options.readtype; if (typeof readtype == 'function') { - readtype = readtype(name) + readtype = readtype(name); } if (readtype === 'image') { if (pathToExtension(name) === 'tga') { - reader.readAsArrayBuffer(file) + reader.readAsArrayBuffer(file); } else { - reader.readAsDataURL(file) + reader.readAsDataURL(file); } } else if (readtype === 'buffer' || readtype === 'binary') { - reader.readAsArrayBuffer(file) + reader.readAsArrayBuffer(file); } /*text*/ else { - reader.readAsText(file) + reader.readAsText(file); } - i++ + i++; } } } - export const read = readFile + export const read = readFile; // MARK: Pick Directory interface PickDirOptions { /**Location where the file dialog starts off */ - startpath?: string + startpath?: string; /** The resource identifier group, used to allow the file dialog (open and save) to remember where it was last used */ - resource_id?: ResourceID + resource_id?: ResourceID; /** Window title for the file picker */ - title?: string + title?: string; } /** * Pick a directory. Desktop app only. @@ -346,27 +346,27 @@ export namespace Filesystem { export function pickDirectory(options: PickDirOptions = {}): string | undefined { if (isApp) { if (!options.startpath && options.resource_id) { - options.startpath = StateMemory.get('dialog_paths')[options.resource_id] + options.startpath = StateMemory.get('dialog_paths')[options.resource_id]; } let dirNames = electron.dialog.showOpenDialogSync(currentwindow, { title: options.title ? options.title : '', properties: ['openDirectory', 'dontAddToRecent'], defaultPath: isStreamerMode() ? app.getPath('desktop') : options.startpath, - }) + }); - if (!dirNames) return null + if (!dirNames) return null; if (options.resource_id) { StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname( dirNames[0] - ) - StateMemory.save('dialog_paths') + ); + StateMemory.save('dialog_paths'); } - return dirNames[0] + return dirNames[0]; } else { - console.warn('Picking directories is currently not supported in the web app') + console.warn('Picking directories is currently not supported in the web app'); } } @@ -375,23 +375,23 @@ export namespace Filesystem { /** * Name of the file type */ - type: string + type: string; /** * File extensions */ - extensions: string[] + extensions: string[]; /** * Suggested file name */ - name?: string + name?: string; /** * Directory path where the file dialog opens */ - startpath?: string + startpath?: string; /** * The resource identifier group, used to allow the file dialog (open and save) to remember where it was last used */ - resource_id?: string + resource_id?: string; } /** * Open a file save dialog to let the user pick a location and name to save a file. On the web app, this might save the file directoy into the downloads folder depending on browser settings. @@ -412,20 +412,20 @@ export namespace Filesystem { resource_id */ if (!isApp) { - let file_name = options.name || 'file' - let extension = pathToExtension(file_name) + let file_name = options.name || 'file'; + let extension = pathToExtension(file_name); if ( options.extensions instanceof Array && !options.extensions.includes(extension) && options.extensions[0] ) { - file_name += '.' + options.extensions[0] + file_name += '.' + options.extensions[0]; } if (options.custom_writer) { - options.custom_writer(options.content, file_name) + options.custom_writer(options.content, file_name); } else { if (options.savetype === 'image') { - saveAs(options.content, file_name, {}) + saveAs(options.content, file_name, {}); } else if ( options.savetype === 'zip' || options.savetype === 'buffer' || @@ -434,30 +434,30 @@ export namespace Filesystem { let blob = options.content instanceof Blob ? options.content - : new Blob([options.content], { type: 'octet/stream' }) - saveAs(blob, file_name) + : new Blob([options.content], { type: 'octet/stream' }); + saveAs(blob, file_name); } else { - let type = 'text/plain;charset=utf-8' + let type = 'text/plain;charset=utf-8'; if (file_name.endsWith('json')) { - type = 'application/json;charset=utf-8' + type = 'application/json;charset=utf-8'; } else if (file_name.endsWith('bbmodel')) { - type = 'model/vnd.blockbench.bbmodel' + type = 'model/vnd.blockbench.bbmodel'; } - let blob = new Blob([options.content], { type }) - saveAs(blob, file_name, { autoBOM: true }) + let blob = new Blob([options.content], { type }); + saveAs(blob, file_name, { autoBOM: true }); } } if (typeof callback === 'function') { - callback(file_name) + callback(file_name); } } else { if (!options.startpath && options.resource_id) { - options.startpath = StateMemory.get('dialog_paths')[options.resource_id] + options.startpath = StateMemory.get('dialog_paths')[options.resource_id]; if (options.name) { options.startpath += osfs + options.name + - (options.extensions ? '.' + options.extensions[0] : '') + (options.extensions ? '.' + options.extensions[0] : ''); } } let file_path = electron.dialog.showSaveDialogSync(currentwindow, { @@ -473,34 +473,35 @@ export namespace Filesystem { : options.startpath && options.startpath !== 'Unknown' ? options.startpath.replace(/\.\w+$/, '') : options.name, - }) - if (!file_path) return + }); + if (!file_path) return; if (options.resource_id) { - StateMemory.get('dialog_paths')[options.resource_id] = PathModule.dirname(file_path) - StateMemory.save('dialog_paths') + StateMemory.get('dialog_paths')[options.resource_id] = + PathModule.dirname(file_path); + StateMemory.save('dialog_paths'); } - let extension = pathToExtension(file_path) + let extension = pathToExtension(file_path); if ( options.extensions instanceof Array && !options.extensions.includes(extension) && options.extensions[0] ) { - file_path += '.' + options.extensions[0] + file_path += '.' + options.extensions[0]; } - writeFile(file_path, options, callback) + writeFile(file_path, options, callback); } } // MARK: Write - type WriteType = 'text' | 'buffer' | 'binary' | 'zip' | 'image' + type WriteType = 'text' | 'buffer' | 'binary' | 'zip' | 'image'; interface WriteOptions { - content?: string | ArrayBuffer | Blob - savetype?: WriteType | ((file: string) => WriteType) + content?: string | ArrayBuffer | Blob; + savetype?: WriteType | ((file: string) => WriteType); custom_writer?: ( content: string | ArrayBuffer | Blob, file_path: string, callback?: (file_path: string) => void - ) => void + ) => void; } /** * Writes a file to the file system. Desktop app only. @@ -511,58 +512,58 @@ export namespace Filesystem { callback?: (file_path: string) => void ) { if (!isApp || !file_path) { - return + return; } if (options.savetype === 'image' && typeof options.content === 'string') { if (options.content.substr(0, 10) === 'data:image') { - fs.writeFileSync(file_path, options.content.split(',')[1], { encoding: 'base64' }) - if (callback) callback(file_path) + fs.writeFileSync(file_path, options.content.split(',')[1], { encoding: 'base64' }); + if (callback) callback(file_path); } else { - let path = options.content.replace(/\?\d+$/, '') + let path = options.content.replace(/\?\d+$/, ''); if (PathModule.relative(path, file_path)) { - fs.copyFileSync(path, file_path) + fs.copyFileSync(path, file_path); } - if (callback) callback(file_path) + if (callback) callback(file_path); } - return + return; } if (options.custom_writer) { - options.custom_writer(options.content, file_path, callback) + options.custom_writer(options.content, file_path, callback); } else if (options.savetype === 'zip') { - let fileReader = new FileReader() + let fileReader = new FileReader(); fileReader.onload = function (event) { - let buffer = Buffer.from(new Uint8Array(this.result as ArrayBuffer)) - fs.writeFileSync(file_path, buffer) + let buffer = Buffer.from(new Uint8Array(this.result as ArrayBuffer)); + fs.writeFileSync(file_path, buffer); if (callback) { - callback(file_path) + callback(file_path); } - } - fileReader.readAsArrayBuffer(options.content as Blob) + }; + fileReader.readAsArrayBuffer(options.content as Blob); } else { //text or binary - let content = options.content + let content = options.content; if (content instanceof ArrayBuffer) { - content = Buffer.from(content) + content = Buffer.from(content); } - fs.writeFileSync(file_path, content as string) + fs.writeFileSync(file_path, content as string); if (callback) { - callback(file_path) + callback(file_path); } } } // MARK: Open export function showFileInFolder(path: string) { - ipcRenderer.send('show-item-in-folder', path) + ipcRenderer.send('show-item-in-folder', path); } // MARK: Find interface FindFileOptions { - recursive: boolean - filter_regex: RegExp - priority_regex?: RegExp - json?: boolean - read_file?: boolean + recursive: boolean; + filter_regex: RegExp; + priority_regex?: RegExp; + json?: boolean; + read_file?: boolean; } /** * Find a file in a directory based on content within the file, optionally optimized via file name match @@ -573,58 +574,58 @@ export namespace Filesystem { options: FindFileOptions, check_file: (path: string, content: string | object) => boolean ) { - let deprioritized_files = [] + let deprioritized_files = []; function checkFile(path) { try { - let content: string - if (options.read_file !== false) content = fs.readFileSync(path, 'utf-8') + let content: string; + if (options.read_file !== false) content = fs.readFileSync(path, 'utf-8'); - return check_file(path, options.json ? autoParseJSON(content, false) : content) + return check_file(path, options.json ? autoParseJSON(content, false) : content); } catch (err) { - console.error(err) - return false + console.error(err); + return false; } } let searchFolder = (path: string) => { - let files + let files; try { - files = fs.readdirSync(path, { withFileTypes: true }) + files = fs.readdirSync(path, { withFileTypes: true }); } catch (err) { - files = [] + files = []; } for (let dirent of files) { - if (dirent.isDirectory()) continue + if (dirent.isDirectory()) continue; if (!options.filter_regex || options.filter_regex.exec(dirent.name)) { - let new_path = path + osfs + dirent.name + let new_path = path + osfs + dirent.name; if (!options.priority_regex || options.priority_regex.exec(dirent.name)) { // priority checking - let result = checkFile(new_path) - if (result) return result + let result = checkFile(new_path); + if (result) return result; } else { - deprioritized_files.push(new_path) + deprioritized_files.push(new_path); } } } if (options.recursive !== false) { for (let dirent of files) { - if (!dirent.isDirectory()) continue + if (!dirent.isDirectory()) continue; - let result = searchFolder(path + osfs + dirent.name) - if (result) return result + let result = searchFolder(path + osfs + dirent.name); + if (result) return result; } } - } + }; for (let directory of base_directories) { - let result = searchFolder(directory) - if (result) return result + let result = searchFolder(directory); + if (result) return result; } for (let path of deprioritized_files) { - let result = checkFile(path) - if (result) return result + let result = checkFile(path); + if (result) return result; } } @@ -633,30 +634,30 @@ export namespace Filesystem { /** * Allowed file extensions */ - extensions: string[] | (() => string[]) + extensions: string[] | (() => string[]); /** * Whether or not to enable the drag handler */ - condition?: ConditionResolvable + condition?: ConditionResolvable; /** * Drop target element */ - element?: string | HTMLElement | (() => string | HTMLElement | boolean) + element?: string | HTMLElement | (() => string | HTMLElement | boolean); /** * If true, the drop will work on all child elements of the specified element */ - propagate?: boolean - readtype?: ReadType + propagate?: boolean; + readtype?: ReadType; /** * Whether to display an error box when importing a dragged file fails */ - errorbox?: boolean + errorbox?: boolean; } export interface DragHandler extends DragHandlerOptions { - cb: (files: FileResult[], event: DragEvent) => void - delete: () => void + cb: (files: FileResult[], event: DragEvent) => void; + delete: () => void; } - export const drag_handlers: Record = {} + export const drag_handlers: Record = {}; /** * Handle files being drag & dropped into Blockbench * @param id ID of the handler @@ -674,56 +675,56 @@ export namespace Filesystem { condition: options.condition, extensions: options.extensions, delete() { - Filesystem.removeDragHandler(id) + Filesystem.removeDragHandler(id); }, - } - if (options.propagate) entry.propagate = true - if (options.readtype) entry.readtype = options.readtype - if (options.errorbox) entry.errorbox = true - if (options.element) entry.element = options.element - - drag_handlers[id] = entry - return entry + }; + if (options.propagate) entry.propagate = true; + if (options.readtype) entry.readtype = options.readtype; + if (options.errorbox) entry.errorbox = true; + if (options.element) entry.element = options.element; + + drag_handlers[id] = entry; + return entry; } export function removeDragHandler(id: string) { - delete drag_handlers[id] + delete drag_handlers[id]; } document.ondragover = function (event) { - event.preventDefault() - } + event.preventDefault(); + }; document.body.ondrop = function (event) { - event.preventDefault() - let text = event.dataTransfer.getData('text/plain') + event.preventDefault(); + let text = event.dataTransfer.getData('text/plain'); if (text && text.startsWith('https://blckbn.ch/')) { - let code = text.replace(/\/$/, '').split('/').last() + let code = text.replace(/\/$/, '').split('/').last(); $.getJSON(`https://blckbn.ch/api/models/${code}`, model => { - Codecs.project.load(model, { path: '' }) + Codecs.project.load(model, { path: '' }); }).fail(error => { - Blockbench.showQuickMessage('message.invalid_link') - }) + Blockbench.showQuickMessage('message.invalid_link'); + }); } forDragHandlers(event, function (handler, el) { - let fileNames = event.dataTransfer.files + let fileNames = event.dataTransfer.files; - let paths: string[] | FileList = [] + let paths: string[] | FileList = []; if (isApp) { for (let file of fileNames) { if ('path' in file) { // @ts-ignore - paths.push(file.path) + paths.push(file.path); } else if (isApp) { // @ts-ignore - let path = webUtils.getPathForFile(file) - paths.push(path) + let path = webUtils.getPathForFile(file); + paths.push(path); } } } else { - paths = fileNames + paths = fileNames; } - if (!paths.length) return + if (!paths.length) return; let read_options = { extensions: (typeof handler.extensions == 'function' @@ -731,24 +732,24 @@ export namespace Filesystem { : handler.extensions) as string[], readtype: handler.readtype, errorbox: handler.errorbox, - } + }; Filesystem.read(paths, read_options, files => { - handler.cb(files, event) - }) - }) - } + handler.cb(files, event); + }); + }); + }; document.body.ondragenter = function (event) { - event.preventDefault() + event.preventDefault(); forDragHandlers(event, function (handler, el) { //$(el).css('background-color', 'red') - }) - } + }); + }; document.body.ondragleave = function (event) { - event.preventDefault() + event.preventDefault(); forDragHandlers(event, function (handler, el) { //$(el).css('background-color', '') - }) - } + }); + }; function forDragHandlers( event: DragEvent, @@ -759,46 +760,46 @@ export namespace Filesystem { event.dataTransfer.files.length == 0 || !event.dataTransfer.files[0].name ) { - return + return; } for (let id in Filesystem.drag_handlers) { - let handler = Filesystem.drag_handlers[id] - let el = undefined + let handler = Filesystem.drag_handlers[id]; + let el = undefined; if (!Condition(handler.condition)) { - continue + continue; } if (!handler.element) { - el = document.body + el = document.body; } else if (typeof handler.element === 'function') { - let result = handler.element() + let result = handler.element(); if (result === true) { - el = $(event.target) + el = $(event.target); } else if ($(result as HTMLElement).length) { - el = $(result as HTMLElement).get(0) + el = $(result as HTMLElement).get(0); } } else if ($(handler.element as HTMLElement).get(0) === event.target) { - el = event.target + el = event.target; } else if (typeof handler.element === 'string' && $(event.target).is(handler.element)) { - el = event.target + el = event.target; } else if (handler.propagate) { - let parent = $(handler.element as HTMLElement) + let parent = $(handler.element as HTMLElement); if (parent && parent.has(event.target as HTMLElement).length) { - el = parent + el = parent; } } let extensions = - typeof handler.extensions == 'function' ? handler.extensions() : handler.extensions - extensions.includes(pathToExtension(event.dataTransfer.files[0].name).toLowerCase()) - let name = event.dataTransfer.files[0].name + typeof handler.extensions == 'function' ? handler.extensions() : handler.extensions; + extensions.includes(pathToExtension(event.dataTransfer.files[0].name).toLowerCase()); + let name = event.dataTransfer.files[0].name; if ( el && extensions.filter(ex => { - return name.substr(-ex.length) == ex + return name.substr(-ex.length) == ex; }).length ) { - cb(handler, el) - break + cb(handler, el); + break; } } } @@ -806,4 +807,4 @@ export namespace Filesystem { Object.assign(window, { Filesystem, -}) +}); diff --git a/js/global_types.ts b/js/global_types.ts index f7bc4c875..1341bd26f 100644 --- a/js/global_types.ts +++ b/js/global_types.ts @@ -1,11 +1,11 @@ -import { ModelFormat as _ModelFormat } from './io/format' +import { ModelFormat as _ModelFormat } from './io/format'; declare global { - const ModelFormat: typeof _ModelFormat - const Format: _ModelFormat - const Formats: Record + const ModelFormat: typeof _ModelFormat; + const Format: _ModelFormat; + const Formats: Record; namespace Blockbench { - const ModelFormat: typeof _ModelFormat - const Format: _ModelFormat + const ModelFormat: typeof _ModelFormat; + const Format: _ModelFormat; } } import { @@ -13,23 +13,23 @@ import { Setting as _Setting, SettingsProfile as _SettingsProfile, Settings as _Settings, -} from './interface/settings' +} from './interface/settings'; declare global { - const settings: typeof _settings - const Setting: typeof _Setting - const SettingsProfile: typeof _SettingsProfile - const Settings: typeof _Settings + const settings: typeof _settings; + const Setting: typeof _Setting; + const SettingsProfile: typeof _SettingsProfile; + const Settings: typeof _Settings; namespace Blockbench { - const settings: typeof _settings - const Setting: typeof _Setting - const SettingsProfile: typeof _SettingsProfile - const Settings: typeof _Settings + const settings: typeof _settings; + const Setting: typeof _Setting; + const SettingsProfile: typeof _SettingsProfile; + const Settings: typeof _Settings; } } -import { Modes as _Modes, Mode as _Mode } from './modes' +import { Modes as _Modes, Mode as _Mode } from './modes'; declare global { - const Modes: typeof _Modes - const Mode: typeof _Mode + const Modes: typeof _Modes; + const Mode: typeof _Mode; } import { Dialog as _Dialog, @@ -38,16 +38,16 @@ import { MessageBox as _MessageBox, ShapelessDialog as _ShapelessDialog, ToolConfig as _ToolConfig, -} from './interface/dialog' +} from './interface/dialog'; declare global { - const Dialog: typeof _Dialog - const ConfigDialog: typeof _ConfigDialog - const DialogSidebar: typeof _DialogSidebar - const MessageBox: typeof _MessageBox - const ShapelessDialog: typeof _ShapelessDialog - const ToolConfig: typeof _ToolConfig + const Dialog: typeof _Dialog; + const ConfigDialog: typeof _ConfigDialog; + const DialogSidebar: typeof _DialogSidebar; + const MessageBox: typeof _MessageBox; + const ShapelessDialog: typeof _ShapelessDialog; + const ToolConfig: typeof _ToolConfig; } -import { Property as _Property } from './util/property' +import { Property as _Property } from './util/property'; declare global { - const Property: typeof _Property + const Property: typeof _Property; } diff --git a/js/interface/dialog.ts b/js/interface/dialog.ts index 2b8cfa0d2..cf4d95c70 100644 --- a/js/interface/dialog.ts +++ b/js/interface/dialog.ts @@ -1,295 +1,295 @@ -import { Blockbench } from '../api' -import { Prop } from '../misc' -import { FormElementOptions, FormResultValue, InputForm, InputFormConfig } from './form' -import { Vue } from './../lib/libs' -import { getStringWidth } from '../util/util' +import { Blockbench } from '../api'; +import { Prop } from '../misc'; +import { FormElementOptions, FormResultValue, InputForm, InputFormConfig } from './form'; +import { Vue } from './../lib/libs'; +import { getStringWidth } from '../util/util'; interface ActionInterface { - name: string - description?: string - icon: string - color?: string - click(event: Event): void - condition?: ConditionResolvable + name: string; + description?: string; + icon: string; + color?: string; + click(event: Event): void; + condition?: ConditionResolvable; } type DialogLineOptions = | HTMLElement | { - label?: string - widget?: Widget | (() => Widget) - nocolon?: boolean - node?: HTMLElement + label?: string; + widget?: Widget | (() => Widget); + nocolon?: boolean; + node?: HTMLElement; } - | string + | string; function buildForm(dialog: Dialog) { - dialog.form = new InputForm(dialog.form_config) - let dialog_content = $(dialog.object).find('.dialog_content') - dialog_content.append(dialog.form.node) - dialog.max_label_width = Math.max(dialog.max_label_width, dialog.form.max_label_width) - if (dialog.form.uses_wide_inputs) dialog.uses_wide_inputs = true + dialog.form = new InputForm(dialog.form_config); + let dialog_content = $(dialog.object).find('.dialog_content'); + dialog_content.append(dialog.form.node); + dialog.max_label_width = Math.max(dialog.max_label_width, dialog.form.max_label_width); + if (dialog.form.uses_wide_inputs) dialog.uses_wide_inputs = true; dialog.form.on('change', ({ result }) => { - if (dialog.onFormChange) dialog.onFormChange(result) - }) + if (dialog.onFormChange) dialog.onFormChange(result); + }); } function buildLines(dialog: Dialog) { - let dialog_content = dialog.object.querySelector('.dialog_content') + let dialog_content = dialog.object.querySelector('.dialog_content'); dialog.lines.forEach(l => { if (typeof l === 'object' && ('label' in l || 'widget' in l)) { - let bar = Interface.createElement('div', { class: 'dialog_bar' }) + let bar = Interface.createElement('div', { class: 'dialog_bar' }); if (l.label) { let label = Interface.createElement( 'label', { class: 'name_space_left' }, tl(l.label) - ) - bar.append(label) + ); + bar.append(label); dialog.max_label_width = Math.max( getStringWidth(label.textContent), dialog.max_label_width - ) + ); } if (l.node) { - bar.append(l.node) + bar.append(l.node); } else if (l.widget) { - let widget: Widget + let widget: Widget; if (typeof l.widget === 'string') { - widget = BarItems[l.widget] + widget = BarItems[l.widget]; } else if (typeof l.widget === 'function') { - widget = l.widget() + widget = l.widget(); } else { - widget = l.widget + widget = l.widget; } - bar.append(widget.getNode()) + bar.append(widget.getNode()); dialog.max_label_width = Math.max( getStringWidth(widget.name), dialog.max_label_width - ) + ); } - dialog.uses_wide_inputs = true - dialog_content.append(bar) + dialog.uses_wide_inputs = true; + dialog_content.append(bar); } else if (typeof l == 'string') { - dialog_content.append(document.createTextNode(l)) + dialog_content.append(document.createTextNode(l)); } else if (l instanceof HTMLElement) { - dialog_content.append(l) + dialog_content.append(l); } - }) + }); } function buildComponent(dialog: Dialog) { - let dialog_content = $(dialog.object).find('.dialog_content').get(0) - let mount: HTMLElement + let dialog_content = $(dialog.object).find('.dialog_content').get(0); + let mount: HTMLElement; // mount_directly, if enabled, skips one layer of wrapper. Class "dialog_content" must be added the the root element of the vue component. if (dialog.component.mount_directly) { - mount = dialog_content + mount = dialog_content; } else { - mount = Interface.createElement('div') - dialog_content.append(mount) + mount = Interface.createElement('div'); + dialog_content.append(mount); } - dialog.component.name = 'dialog-content' - dialog.content_vue = new Vue(dialog.component).$mount(mount) + dialog.component.name = 'dialog-content'; + dialog.content_vue = new Vue(dialog.component).$mount(mount); } function buildToolbars(dialog: Dialog) { - let dialog_content = $(dialog.object).find('.dialog_content') + let dialog_content = $(dialog.object).find('.dialog_content'); for (let id in dialog.toolbars) { - let toolbar = dialog.toolbars[id] - dialog_content.append(toolbar.node) + let toolbar = dialog.toolbars[id]; + dialog_content.append(toolbar.node); } } -const toggle_sidebar = window.innerWidth < 640 +const toggle_sidebar = window.innerWidth < 640; interface DialogSidebarOptions { pages?: { - [key: string]: string | { label: string; icon: IconString; color?: string } | MenuSeparator - } - page?: string - actions?: (Action | ActionInterface | string)[] - onPageSwitch?(page: string): void + [key: string]: string | { label: string; icon: IconString; color?: string } | MenuSeparator; + }; + page?: string; + actions?: (Action | ActionInterface | string)[]; + onPageSwitch?(page: string): void; } export class DialogSidebar { - open: boolean + open: boolean; pages: { - [key: string]: string | { label: string; icon: IconString; color?: string } | MenuSeparator - } - page: string - actions: (Action | ActionInterface | string)[] - dialog: Dialog + [key: string]: string | { label: string; icon: IconString; color?: string } | MenuSeparator; + }; + page: string; + actions: (Action | ActionInterface | string)[]; + dialog: Dialog; - node: HTMLDivElement - page_menu: Record - onPageSwitch?(page: string): void + node: HTMLDivElement; + page_menu: Record; + onPageSwitch?(page: string): void; constructor(options: DialogSidebarOptions, dialog: Dialog) { - this.open = !toggle_sidebar - this.pages = options.pages || {} - this.page = options.page || Object.keys(this.pages)[0] - this.actions = options.actions || [] - this.dialog = dialog - this.onPageSwitch = options.onPageSwitch || null + this.open = !toggle_sidebar; + this.pages = options.pages || {}; + this.page = options.page || Object.keys(this.pages)[0]; + this.actions = options.actions || []; + this.dialog = dialog; + this.onPageSwitch = options.onPageSwitch || null; } build() { - this.node = document.createElement('div') - this.node.className = 'dialog_sidebar' + this.node = document.createElement('div'); + this.node.className = 'dialog_sidebar'; - let page_list = document.createElement('ul') - page_list.className = 'dialog_sidebar_pages' - this.node.append(page_list) - this.page_menu = {} + let page_list = document.createElement('ul'); + page_list.className = 'dialog_sidebar_pages'; + this.node.append(page_list); + this.page_menu = {}; for (let key in this.pages) { - let page = this.pages[key] + let page = this.pages[key]; if (page instanceof MenuSeparator) { - let expander = Interface.createElement('span') + let expander = Interface.createElement('span'); // @ts-ignore I don't even know what typescript is thinking here let node = Interface.createElement( 'div', { class: 'dialog_sidebar_separator' }, page ? [page.label, expander] : expander - ) - page_list.append(node) - continue + ); + page_list.append(node); + continue; } - let li = document.createElement('li') + let li = document.createElement('li'); if (typeof page == 'object' && page.icon) { - li.append(Blockbench.getIconNode(page.icon, page.color)) + li.append(Blockbench.getIconNode(page.icon, page.color)); } - li.append(typeof page == 'string' ? tl(page) : tl(page.label)) - li.setAttribute('page', key) - if (this.page == key) li.classList.add('selected') - this.page_menu[key] = li + li.append(typeof page == 'string' ? tl(page) : tl(page.label)); + li.setAttribute('page', key); + if (this.page == key) li.classList.add('selected'); + this.page_menu[key] = li; li.addEventListener('click', event => { - this.setPage(key) - if (toggle_sidebar) this.toggle() - }) - page_list.append(li) + this.setPage(key); + if (toggle_sidebar) this.toggle(); + }); + page_list.append(li); } if (this.actions.length) { - let action_list = document.createElement('ul') - action_list.className = 'dialog_sidebar_actions' - this.node.append(action_list) + let action_list = document.createElement('ul'); + action_list.className = 'dialog_sidebar_actions'; + this.node.append(action_list); this.actions.forEach(action => { if (typeof action == 'string') { - action = BarItems[action] as Action + action = BarItems[action] as Action; } - let copy + let copy; if (action instanceof Action) { - copy = action.menu_node.cloneNode(true) + copy = action.menu_node.cloneNode(true); copy.addEventListener('click', event => { - action.trigger(event) - }) + action.trigger(event); + }); } else { - copy = document.createElement('li') - copy.title = action.description ? tl(action.description) : '' - let icon = Blockbench.getIconNode(action.icon, action.color) - let span = document.createElement('span') - span.textContent = tl(action.name) - copy.append(icon) - copy.append(span) + copy = document.createElement('li'); + copy.title = action.description ? tl(action.description) : ''; + let icon = Blockbench.getIconNode(action.icon, action.color); + let span = document.createElement('span'); + span.textContent = tl(action.name); + copy.append(icon); + copy.append(span); copy.addEventListener('click', event => { - action.click(event) - }) + action.click(event); + }); } - action_list.append(copy) - }) + action_list.append(copy); + }); } - this.toggle(this.open) + this.toggle(this.open); - this.dialog.object.querySelector('div.dialog_wrapper').append(this.node) - return this.node + this.dialog.object.querySelector('div.dialog_wrapper').append(this.node); + return this.node; } toggle(state = !this.open) { - this.open = state + this.open = state; if (this.node.parentElement) { - this.node.parentElement.classList.toggle('has_sidebar', this.open) + this.node.parentElement.classList.toggle('has_sidebar', this.open); } } setPage(page: string) { - let allow - if (this.onPageSwitch) allow = this.onPageSwitch(page) - if (allow === false) return - this.page = page + let allow; + if (this.onPageSwitch) allow = this.onPageSwitch(page); + if (allow === false) return; + this.page = page; for (let key in this.page_menu) { - let li = this.page_menu[key] - li.classList.toggle('selected', key == this.page) + let li = this.page_menu[key]; + li.classList.toggle('selected', key == this.page); } } } interface DialogOptions { - title: string - id?: string - icon?: IconString - width?: number + title: string; + id?: string; + icon?: IconString; + width?: number; /** * Unless set to false, clicking on the darkened area outside of the dialog will cancel the dialog. */ - cancel_on_click_outside?: boolean + cancel_on_click_outside?: boolean; /** * Default button to press to confirm the dialog. Defaults to the first button. */ - confirmIndex?: number + confirmIndex?: number; /** * Default button to press to cancel the dialog. Defaults to the last button. */ - cancelIndex?: number + cancelIndex?: number; /** * Function to execute when the dialog is opened */ - onOpen?(): void + onOpen?(): void; /** * Function to execute when the user confirms the dialog */ - onConfirm?(formResult: any, event: Event): void | boolean + onConfirm?(formResult: any, event: Event): void | boolean; /** * Function to execute when the user cancels the dialog */ - onCancel?(event: Event): void | boolean + onCancel?(event: Event): void | boolean; /** * Triggered when the user presses a specific button */ - onButton?(button_index: number, event?: Event): void | boolean + onButton?(button_index: number, event?: Event): void | boolean; /** * Triggered when the user attemps to close the dialog */ - onClose?(button_index: number, event?: Event): void | boolean + onClose?(button_index: number, event?: Event): void | boolean; /** * Runs when the dialog is resized */ - onResize?(): void + onResize?(): void; /** * Runs when the dialog is built */ - onBuild?(): void + onBuild?(): void; /** * Function to run when anything in the form is changed */ - onFormChange?(form_result: { [key: string]: FormResultValue }): void + onFormChange?(form_result: { [key: string]: FormResultValue }): void; /** * Array of HTML any strings for each line of content in the dialog. */ - lines?: DialogLineOptions[] + lines?: DialogLineOptions[]; /** * Creates a form in the dialog */ - form?: InputFormConfig + form?: InputFormConfig; /** * Vue component */ - component?: Vue.Component + component?: Vue.Component; /** * Order that the different interface types appear in the dialog. Default is 'form', 'lines', 'component'. */ - part_order?: string[] - form_first?: boolean + part_order?: string[]; + form_first?: boolean; /** * Creates a dialog sidebar */ - sidebar?: DialogSidebarOptions - toolbars?: Record + sidebar?: DialogSidebarOptions; + toolbars?: Record; /** * Menu in the handle bar */ - title_menu?: Menu + title_menu?: Menu; /** * Display a progress bar in the dialog */ @@ -297,174 +297,174 @@ interface DialogOptions { /** * A progress value between 0 and 1 */ - progress?: number - } + progress?: number; + }; /** * If true, the dialog will only have one button to close it */ - singleButton?: boolean + singleButton?: boolean; /** * List of buttons */ - buttons?: string[] + buttons?: string[]; /** * A list of keyboard shortcuts that only work inside the dialog */ keyboard_actions?: { [id: string]: { - keybind: Keybind - run: (event: KeyboardEvent) => void - condition?: ConditionResolvable - } - } + keybind: Keybind; + run: (event: KeyboardEvent) => void; + condition?: ConditionResolvable; + }; + }; /** * Select on which axes the dialog can be resized. None by default */ - resizable?: 'x' | 'y' | 'xy' | boolean + resizable?: 'x' | 'y' | 'xy' | boolean; /** * Set to false to stop the dialog from being dragged around */ - draggable?: false + draggable?: false; /** * Create a dark backdrop behind the dialog */ - darken?: boolean + darken?: boolean; } export class Dialog { - id: string - title: string - object: HTMLElement - content_vue: Vue | null + id: string; + title: string; + object: HTMLElement; + content_vue: Vue | null; progress_bar?: { /** * The current progress */ - progress?: number + progress?: number; /** * Set the progress displayed in the progress bar * @param value A progress value between 0 and 1 */ - setProgress(value: number): void + setProgress(value: number): void; /** * The progress bar HTML node */ - node?: HTMLDivElement - } - - confirmIndex: number - cancelIndex: number - - lines?: DialogLineOptions[] - form?: InputForm - component?: Vue.Component - part_order?: string[] - form_first?: boolean - sidebar?: DialogSidebar - title_menu?: Menu - singleButton?: boolean - buttons?: string[] + node?: HTMLDivElement; + }; + + confirmIndex: number; + cancelIndex: number; + + lines?: DialogLineOptions[]; + form?: InputForm; + component?: Vue.Component; + part_order?: string[]; + form_first?: boolean; + sidebar?: DialogSidebar; + title_menu?: Menu; + singleButton?: boolean; + buttons?: string[]; keyboard_actions?: { [id: string]: { - keybind: Keybind - run: (event: KeyboardEvent) => void - condition?: ConditionResolvable - } - } - resizable?: 'x' | 'y' | 'xy' | false - - configuration: DialogOptions - toolbars: Record - form_config: InputFormConfig - width: number - draggable: boolean - darken: boolean - cancel_on_click_outside: boolean - max_label_width?: number - uses_wide_inputs?: boolean - onConfirm?(formResult: any, event: Event): void | boolean - onCancel?(event: Event): void | boolean - onButton?(button_index: number, event?: Event): void | boolean - onFormChange?(form_result: { [key: string]: FormResultValue }): void - onOpen: () => void - onBuild: (object: HTMLElement) => void - onResize: () => void - - constructor(options: DialogOptions) - constructor(id: string, options: DialogOptions) + keybind: Keybind; + run: (event: KeyboardEvent) => void; + condition?: ConditionResolvable; + }; + }; + resizable?: 'x' | 'y' | 'xy' | false; + + configuration: DialogOptions; + toolbars: Record; + form_config: InputFormConfig; + width: number; + draggable: boolean; + darken: boolean; + cancel_on_click_outside: boolean; + max_label_width?: number; + uses_wide_inputs?: boolean; + onConfirm?(formResult: any, event: Event): void | boolean; + onCancel?(event: Event): void | boolean; + onButton?(button_index: number, event?: Event): void | boolean; + onFormChange?(form_result: { [key: string]: FormResultValue }): void; + onOpen: () => void; + onBuild: (object: HTMLElement) => void; + onResize: () => void; + + constructor(options: DialogOptions); + constructor(id: string, options: DialogOptions); constructor(id: string | DialogOptions, options?: DialogOptions) { if (typeof id == 'object') { - options = id - id = options.id + options = id; + id = options.id; } - this.id = id - this.title = options.title - - this.lines = options.lines - this.toolbars = options.toolbars - this.form_config = options.form - this.component = options.component - this.content_vue = null + this.id = id; + this.title = options.title; + + this.lines = options.lines; + this.toolbars = options.toolbars; + this.form_config = options.form; + this.component = options.component; + this.content_vue = null; this.part_order = options.part_order || - (options.form_first ? ['form', 'lines', 'component'] : ['lines', 'form', 'component']) + (options.form_first ? ['form', 'lines', 'component'] : ['lines', 'form', 'component']); - this.sidebar = options.sidebar ? new DialogSidebar(options.sidebar, this) : null - this.title_menu = options.title_menu || null + this.sidebar = options.sidebar ? new DialogSidebar(options.sidebar, this) : null; + this.title_menu = options.title_menu || null; if (options.progress_bar) { this.progress_bar = { setProgress: (progress: number) => { - this.progress_bar.progress = progress + this.progress_bar.progress = progress; if (this.progress_bar.node) { - this.progress_bar.node.style.setProperty('--progress', progress.toString()) + this.progress_bar.node.style.setProperty('--progress', progress.toString()); } }, progress: options.progress_bar.progress ?? 0, node: null, - } + }; } - this.width = options.width - this.draggable = options.draggable - this.resizable = options.resizable === true ? 'xy' : options.resizable - this.darken = options.darken !== false - this.cancel_on_click_outside = options.cancel_on_click_outside !== false - this.singleButton = options.singleButton + this.width = options.width; + this.draggable = options.draggable; + this.resizable = options.resizable === true ? 'xy' : options.resizable; + this.darken = options.darken !== false; + this.cancel_on_click_outside = options.cancel_on_click_outside !== false; + this.singleButton = options.singleButton; this.buttons = options.buttons instanceof Array ? options.buttons : options.singleButton ? ['dialog.close'] - : ['dialog.confirm', 'dialog.cancel'] - this.form_first = options.form_first - this.confirmIndex = options.confirmIndex || 0 + : ['dialog.confirm', 'dialog.cancel']; + this.form_first = options.form_first; + this.confirmIndex = options.confirmIndex || 0; this.cancelIndex = - options.cancelIndex !== undefined ? options.cancelIndex : this.buttons.length - 1 - this.keyboard_actions = options.keyboard_actions || {} - - this.onConfirm = options.onConfirm - this.onCancel = options.onCancel - this.onButton = options.onButton || options.onClose - this.onFormChange = options.onFormChange - this.onOpen = options.onOpen - this.onBuild = options.onBuild - this.onResize = options.onResize - - this.object + options.cancelIndex !== undefined ? options.cancelIndex : this.buttons.length - 1; + this.keyboard_actions = options.keyboard_actions || {}; + + this.onConfirm = options.onConfirm; + this.onCancel = options.onCancel; + this.onButton = options.onButton || options.onClose; + this.onFormChange = options.onFormChange; + this.onOpen = options.onOpen; + this.onBuild = options.onBuild; + this.onResize = options.onResize; + + this.object; } /** * Triggers the confirm event of the dialog. */ confirm(event?: Event) { - this.close(this.confirmIndex, event) + this.close(this.confirmIndex, event); } /** * Triggers the cancel event of the dialog. */ cancel(event?: Event) { - this.close(this.cancelIndex, event) + this.close(this.cancelIndex, event); } updateFormValues(initial?: boolean) { - return this.form.getResult() + return this.form.getResult(); } /** * Set the values of the dialog form inputs @@ -472,7 +472,7 @@ export class Dialog { * @param update Whether to update the dialog (call onFormChange) after setting the values. Default is true. Set to false when called from onFormChange */ setFormValues(values: Record, update = true) { - this.form.setValues(values, update) + this.form.setValues(values, update); } /** * Set whether the dialog form inputs are toggled on or off. See "toggle_enabled" @@ -480,93 +480,93 @@ export class Dialog { * @param update Whether to update the dialog (call onFormChange) after setting the values. Default is true. Set to false when called from onFormChange */ setFormToggles(values: Record, update = true) { - this.form.setToggles(values, update) + this.form.setToggles(values, update); } getFormResult() { - return this.form?.getResult() + return this.form?.getResult(); } close(button: number = this.cancelIndex, event?: Event) { if (button == this.confirmIndex && typeof this.onConfirm == 'function') { - let formResult = this.getFormResult() ?? {} - let result = this.onConfirm(formResult, event) - if (result === false) return + let formResult = this.getFormResult() ?? {}; + let result = this.onConfirm(formResult, event); + if (result === false) return; } if (button == this.cancelIndex && typeof this.onCancel == 'function') { - let result = this.onCancel(event) - if (result === false) return + let result = this.onCancel(event); + if (result === false) return; } if (typeof this.onButton == 'function') { - let result = this.onButton(button, event) - if (result === false) return + let result = this.onButton(button, event); + if (result === false) return; } - this.hide() + this.hide(); } build() { - if (this.object) this.object.remove() - this.object = document.createElement('dialog') - this.object.className = 'dialog' - this.object.id = this.id + if (this.object) this.object.remove(); + this.object = document.createElement('dialog'); + this.object.className = 'dialog'; + this.object.id = this.id; - let handle = document.createElement('div') - handle.className = 'dialog_handle' - this.object.append(handle) + let handle = document.createElement('div'); + handle.className = 'dialog_handle'; + this.object.append(handle); if (this.title_menu) { - let menu_button = document.createElement('div') - menu_button.className = 'dialog_menu_button' - menu_button.append(Blockbench.getIconNode('expand_more')) + let menu_button = document.createElement('div'); + menu_button.className = 'dialog_menu_button'; + menu_button.append(Blockbench.getIconNode('expand_more')); menu_button.addEventListener('click', event => { - this.title_menu.open(menu_button) - }) - handle.append(menu_button) + this.title_menu.open(menu_button); + }); + handle.append(menu_button); } - let title = document.createElement('div') - title.className = 'dialog_title' - title.textContent = tl(this.title) - handle.append(title) + let title = document.createElement('div'); + title.className = 'dialog_title'; + title.textContent = tl(this.title); + handle.append(title); - let jq_dialog = $(this.object) - this.max_label_width = 140 - this.uses_wide_inputs = false + let jq_dialog = $(this.object); + this.max_label_width = 140; + this.uses_wide_inputs = false; - let wrapper = document.createElement('div') - wrapper.className = 'dialog_wrapper' + let wrapper = document.createElement('div'); + wrapper.className = 'dialog_wrapper'; - let content = document.createElement('content') - content.className = 'dialog_content' - this.object.append(wrapper) + let content = document.createElement('content'); + content.className = 'dialog_content'; + this.object.append(wrapper); if (this.sidebar) { if (window.innerWidth < 920) { - let menu_button = document.createElement('div') - menu_button.className = 'dialog_sidebar_menu_button' - menu_button.append(Blockbench.getIconNode('menu')) + let menu_button = document.createElement('div'); + menu_button.className = 'dialog_sidebar_menu_button'; + menu_button.append(Blockbench.getIconNode('menu')); menu_button.addEventListener('click', event => { - this.sidebar.toggle() - }) - handle.prepend(menu_button) + this.sidebar.toggle(); + }); + handle.prepend(menu_button); } - this.sidebar.build() - wrapper.classList.toggle('has_sidebar', this.sidebar.open) + this.sidebar.build(); + wrapper.classList.toggle('has_sidebar', this.sidebar.open); } - wrapper.append(content) + wrapper.append(content); this.part_order.forEach(part => { - if (part == 'form' && this.form_config) buildForm(this) - if (part == 'lines' && this.lines) buildLines(this) - if (part == 'toolbars' && this.toolbars) buildToolbars(this) - if (part == 'component' && this.component) buildComponent(this) - }) + if (part == 'form' && this.form_config) buildForm(this); + if (part == 'lines' && this.lines) buildLines(this); + if (part == 'toolbars' && this.toolbars) buildToolbars(this); + if (part == 'component' && this.component) buildComponent(this); + }); if (this.max_label_width) { - let width = this.width || 540 + let width = this.width || 540; let max_width = this.uses_wide_inputs ? Math.clamp(this.max_label_width + 9, 0, width / 2) - : Math.clamp(this.max_label_width + 16, 0, width - 100) - this.object.style.setProperty('--max_label_width', max_width + 'px') + : Math.clamp(this.max_label_width + 16, 0, width - 100); + this.object.style.setProperty('--max_label_width', max_width + 'px'); } if (this.progress_bar) { @@ -574,101 +574,101 @@ export class Dialog { 'div', { class: 'progress_bar' }, Interface.createElement('div', { class: 'progress_bar_inner' }) - ) as HTMLDivElement - this.progress_bar.setProgress(this.progress_bar.progress) - this.object.querySelector('content.dialog_content').append(this.progress_bar.node) + ) as HTMLDivElement; + this.progress_bar.setProgress(this.progress_bar.progress); + this.object.querySelector('content.dialog_content').append(this.progress_bar.node); } if (this.buttons.length) { - let buttons = [] + let buttons = []; this.buttons.forEach((b, i) => { - let btn = Interface.createElement('button', { type: 'button' }, tl(b)) - buttons.push(btn) + let btn = Interface.createElement('button', { type: 'button' }, tl(b)); + buttons.push(btn); btn.addEventListener('click', event => { - this.close(i, event) - }) - }) - buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn') - buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn') - let button_bar = $('
    ') + this.close(i, event); + }); + }); + buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn'); + buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn'); + let button_bar = $('
    '); buttons.forEach((button, i) => { - button_bar.append(button) - }) + button_bar.append(button); + }); - wrapper.append(button_bar[0]) + wrapper.append(button_bar[0]); } - let close_button = document.createElement('div') - close_button.classList.add('dialog_close_button') - close_button.innerHTML = 'clear' - jq_dialog.append(close_button) + let close_button = document.createElement('div'); + close_button.classList.add('dialog_close_button'); + close_button.innerHTML = 'clear'; + jq_dialog.append(close_button); close_button.addEventListener('click', e => { - this.cancel() - }) + this.cancel(); + }); //Draggable if (this.draggable !== false) { - jq_dialog.addClass('draggable') + jq_dialog.addClass('draggable'); // @ts-ignore Draggable library doesn't have types jq_dialog.draggable({ handle: '.dialog_handle', containment: '#page_wrapper', - }) - jq_dialog.css('position', 'absolute') + }); + jq_dialog.css('position', 'absolute'); } if (this.resizable) { - this.object.classList.add('resizable') - let resize_handle = Interface.createElement('div', { class: 'dialog_resize_handle' }) - jq_dialog.append(resize_handle) + this.object.classList.add('resizable'); + let resize_handle = Interface.createElement('div', { class: 'dialog_resize_handle' }); + jq_dialog.append(resize_handle); if (this.resizable == 'x') { - resize_handle.style.cursor = 'e-resize' + resize_handle.style.cursor = 'e-resize'; } else if (this.resizable == 'y') { - resize_handle.style.cursor = 's-resize' + resize_handle.style.cursor = 's-resize'; } addEventListeners(resize_handle, 'mousedown touchstart', (e1: PointerEvent) => { - convertTouchEvent(e1) - resize_handle.classList.add('dragging') + convertTouchEvent(e1); + resize_handle.classList.add('dragging'); - let start_position = [e1.clientX, e1.clientY] - if (!this.width) this.width = this.object.clientWidth - let original_width = this.width - let original_left = parseFloat(this.object.style.left) + let start_position = [e1.clientX, e1.clientY]; + if (!this.width) this.width = this.object.clientWidth; + let original_width = this.width; + let original_left = parseFloat(this.object.style.left); let original_height = - parseFloat(this.object.style.height) || this.object.clientHeight + parseFloat(this.object.style.height) || this.object.clientHeight; let move = (e2: PointerEvent) => { - convertTouchEvent(e2) + convertTouchEvent(e2); if (this.resizable && this.resizable.includes('x')) { - let x_offset = e2.clientX - start_position[0] - this.width = original_width + x_offset * 2 - this.object.style.width = this.width + 'px' + let x_offset = e2.clientX - start_position[0]; + this.width = original_width + x_offset * 2; + this.object.style.width = this.width + 'px'; if (this.draggable !== false) { this.object.style.left = Math.clamp( original_left - (this.object.clientWidth - original_width) / 2, 0, window.innerWidth - ) + 'px' + ) + 'px'; } } if (this.resizable && this.resizable.includes('y')) { - let y_offset = e2.clientY - start_position[1] - let height = Math.clamp(original_height + y_offset, 80, window.innerHeight) - this.object.style.height = height + 'px' + let y_offset = e2.clientY - start_position[1]; + let height = Math.clamp(original_height + y_offset, 80, window.innerHeight); + this.object.style.height = height + 'px'; } if (typeof this.onResize == 'function') { - this.onResize() + this.onResize(); } - } + }; let stop = e2 => { - removeEventListeners(document, 'mousemove touchmove', move) - removeEventListeners(document, 'mouseup touchend', stop) - resize_handle.classList.remove('dragging') - } - addEventListeners(document, 'mousemove touchmove', move) - addEventListeners(document, 'mouseup touchend', stop) - }) + removeEventListeners(document, 'mousemove touchmove', move); + removeEventListeners(document, 'mouseup touchend', stop); + resize_handle.classList.remove('dragging'); + }; + addEventListeners(document, 'mousemove touchmove', move); + addEventListeners(document, 'mouseup touchend', stop); + }); } let sanitizePosition = () => { if ( @@ -679,20 +679,20 @@ export class Dialog { Math.max( Interface.page_wrapper.clientHeight - this.object.clientHeight + 26, 26 - ) + 'px' + ) + 'px'; } - } - sanitizePosition() - this.resize_observer = new ResizeObserver(sanitizePosition) - this.resize_observer.observe(this.object) + }; + sanitizePosition(); + this.resize_observer = new ResizeObserver(sanitizePosition); + this.resize_observer.observe(this.object); if (typeof this.onBuild == 'function') { - this.onBuild(this.object) + this.onBuild(this.object); } - return this + return this; } - private resize_observer: ResizeObserver + private resize_observer: ResizeObserver; show(anchor?: HTMLElement): this { // Hide previous @@ -702,232 +702,232 @@ export class Dialog { open_interface instanceof Dialog == false && typeof open_interface.hide == 'function' ) { - open_interface.hide() + open_interface.hide(); } if (!this.object) { - this.build() + this.build(); } else if (this.form) { - this.form.updateValues({ cause: 'setup' }) + this.form.updateValues({ cause: 'setup' }); } - let jq_dialog = $(this.object) + let jq_dialog = $(this.object); - document.getElementById('dialog_wrapper').append(this.object) + document.getElementById('dialog_wrapper').append(this.object); if (this instanceof ShapelessDialog === false) { - this.object.style.display = 'flex' + this.object.style.display = 'flex'; this.object.style.top = - limitNumber(window.innerHeight / 2 - this.object.clientHeight / 2, 0, 100) + 'px' + limitNumber(window.innerHeight / 2 - this.object.clientHeight / 2, 0, 100) + 'px'; if (this.width) { - this.object.style.width = this.width + 'px' + this.object.style.width = this.width + 'px'; } if (this.draggable !== false) { - let x = Math.clamp((window.innerWidth - this.object.clientWidth) / 2, 0, 2000) - this.object.style.left = x + 'px' + let x = Math.clamp((window.innerWidth - this.object.clientWidth) / 2, 0, 2000); + this.object.style.left = x + 'px'; } } if (!Blockbench.isTouch) { - let first_focus = jq_dialog.find('.focusable_input').first() - if (first_focus) first_focus.trigger('focus') + let first_focus = jq_dialog.find('.focusable_input').first(); + if (first_focus) first_focus.trigger('focus'); } if (typeof this.onOpen == 'function') { - this.onOpen() + this.onOpen(); } - this.focus() + this.focus(); setTimeout(() => { - this.object.style.setProperty('--dialog-height', this.object.clientHeight + 'px') - this.object.style.setProperty('--dialog-width', this.object.clientWidth + 'px') - }, 1) + this.object.style.setProperty('--dialog-height', this.object.clientHeight + 'px'); + this.object.style.setProperty('--dialog-width', this.object.clientWidth + 'px'); + }, 1); - return this + return this; } focus() { - Dialog.stack.remove(this) - let blackout = document.getElementById('blackout') - blackout.style.display = 'block' - blackout.classList.toggle('darken', this.darken) - blackout.style.zIndex = (20 + Dialog.stack.length * 2).toString() - this.object.style.zIndex = (21 + Dialog.stack.length * 2).toString() - - Prop._previous_active_panel = Prop.active_panel - Prop.active_panel = 'dialog' + Dialog.stack.remove(this); + let blackout = document.getElementById('blackout'); + blackout.style.display = 'block'; + blackout.classList.toggle('darken', this.darken); + blackout.style.zIndex = (20 + Dialog.stack.length * 2).toString(); + this.object.style.zIndex = (21 + Dialog.stack.length * 2).toString(); + + Prop._previous_active_panel = Prop.active_panel; + Prop.active_panel = 'dialog'; // @ts-ignore - window.open_dialog = this.id + window.open_dialog = this.id; // @ts-ignore - window.open_interface = this - Dialog.open = this - Dialog.stack.push(this) + window.open_interface = this; + Dialog.open = this; + Dialog.stack.push(this); } hide() { - $('#blackout').hide().toggleClass('darken', true) - $(this.object).hide() + $('#blackout').hide().toggleClass('darken', true); + $(this.object).hide(); // @ts-ignore - window.open_dialog = false + window.open_dialog = false; // @ts-ignore - window.open_interface = false - Dialog.open = null - Dialog.stack.remove(this) - Prop.active_panel = Prop._previous_active_panel - $(this.object).detach() + window.open_interface = false; + Dialog.open = null; + Dialog.stack.remove(this); + Prop.active_panel = Prop._previous_active_panel; + $(this.object).detach(); if (Dialog.stack.length) { - Dialog.stack.last().focus() + Dialog.stack.last().focus(); } - return this + return this; } delete() { - $(this.object).remove() + $(this.object).remove(); if (this.content_vue) { - this.content_vue.$destroy() - delete this.content_vue + this.content_vue.$destroy(); + delete this.content_vue; } } getFormBar(form_id: string) { - var bar = $(this.object).find(`.form_bar_${form_id}`) - if (bar.length) return bar + var bar = $(this.object).find(`.form_bar_${form_id}`); + if (bar.length) return bar; } /** * Currently opened dialog */ - static open: Dialog | null = null + static open: Dialog | null = null; /** * Stack of currently open dialogs, ordered by depth */ - static stack: Dialog[] = [] + static stack: Dialog[] = []; } interface ShapelessDialogOptions { - title: string + title: string; /** * Default button to press to confirm the dialog. Defaults to the first button. */ - confirmIndex?: number + confirmIndex?: number; /** * Default button to press to cancel the dialog. Defaults to the last button. */ - cancelIndex?: number + cancelIndex?: number; /** * Function to execute when the user confirms the dialog */ - onConfirm?(formResult: any): void + onConfirm?(formResult: any): void; /** * Function to execute when the user cancels the dialog */ - onCancel?(): void + onCancel?(): void; /** * Triggered when the user presses a specific button */ - onClose?(button_index: number, event?: Event): void + onClose?(button_index: number, event?: Event): void; /** * Vue component */ - component?: Vue.Component + component?: Vue.Component; /** * Unless set to false, clicking on the darkened area outside of the dialog will cancel the dialog. */ - cancel_on_click_outside?: boolean + cancel_on_click_outside?: boolean; } export class ShapelessDialog extends Dialog { - onClose?: (event: Event) => void | boolean - onConfirm?: (event: Event) => void | boolean + onClose?: (event: Event) => void | boolean; + onConfirm?: (event: Event) => void | boolean; constructor(id: string, options: ShapelessDialogOptions) { - super(id, options) + super(id, options); // @ts-ignore - if (options.build) this.build = options.build + if (options.build) this.build = options.build; // @ts-ignore - if (options.onClose) this.onClose = options.onClose + if (options.onClose) this.onClose = options.onClose; } close(button: number, event: Event) { if (button == this.confirmIndex && typeof this.onConfirm == 'function') { - let result = this.onConfirm(event) - if (result === false) return + let result = this.onConfirm(event); + if (result === false) return; } if (button == this.cancelIndex && typeof this.onCancel == 'function') { - let result = this.onCancel(event) - if (result === false) return + let result = this.onCancel(event); + if (result === false) return; } if (typeof this.onClose == 'function') { - let result = this.onClose(event) - if (result === false) return + let result = this.onClose(event); + if (result === false) return; } - this.hide() + this.hide(); } show(): this { - super.show() - $(this.object).show() - return this + super.show(); + $(this.object).show(); + return this; } build(): this { - this.object = Interface.createElement('div', { id: this.id, class: 'shapeless_dialog' }) + this.object = Interface.createElement('div', { id: this.id, class: 'shapeless_dialog' }); if (this.component) { - this.component.name = 'dialog-content' - this.content_vue = new Vue(this.component).$mount(this.object, true) + this.component.name = 'dialog-content'; + this.content_vue = new Vue(this.component).$mount(this.object, true); } - return this + return this; } delete() { - if (this.object) this.object.remove() - this.object = null + if (this.object) this.object.remove(); + this.object = null; } } type MessageBoxCommandOptions = | string | { - text: string - icon?: IconString - condition?: ConditionResolvable - description?: string - } + text: string; + icon?: IconString; + condition?: ConditionResolvable; + description?: string; + }; type MessageBoxCheckbox = | string | { - value?: boolean - condition: ConditionResolvable - text: string - } + value?: boolean; + condition: ConditionResolvable; + text: string; + }; export interface MessageBoxOptions { /** * Index of the confirm button within the buttons array */ - confirm?: number + confirm?: number; /** * Index of the cancel button within the buttons array */ - cancel?: number - buttons?: string[] - translateKey?: string - title?: string - message?: string - icon?: string - width?: number - cancelIndex?: number - confirmIndex?: number + cancel?: number; + buttons?: string[]; + translateKey?: string; + title?: string; + message?: string; + icon?: string; + width?: number; + cancelIndex?: number; + confirmIndex?: number; /** * Display a list of actions to do in the dialog. When clicked, the message box closes with the string ID of the command as first argument. */ - commands?: Record + commands?: Record; /** * Adds checkboxes to the bottom of the message box */ - checkboxes?: Record + checkboxes?: Record; } export class MessageBox extends Dialog { // @ts-ignore We should rewrite this to use a common internal DialogBase class - declare configuration: MessageBoxOptions + declare configuration: MessageBoxOptions; callback?: ( button: number | string, result?: Record, event?: Event - ) => void | boolean + ) => void | boolean; constructor( options: MessageBoxOptions, @@ -937,33 +937,33 @@ export class MessageBox extends Dialog { event?: Event ) => void | boolean ) { - super('message_box', options as DialogOptions) - this.configuration = options - if (!options.buttons) this.buttons = ['dialog.ok'] - this.cancelIndex = Math.min(this.buttons.length - 1, this.cancelIndex) - this.confirmIndex = Math.min(this.buttons.length - 1, this.confirmIndex) - this.callback = callback + super('message_box', options as DialogOptions); + this.configuration = options; + if (!options.buttons) this.buttons = ['dialog.ok']; + this.cancelIndex = Math.min(this.buttons.length - 1, this.cancelIndex); + this.confirmIndex = Math.min(this.buttons.length - 1, this.confirmIndex); + this.callback = callback; } // @ts-ignore close(button: number | string, result: Record, event: Event) { if (this.callback) { - let allow_close = this.callback(button, result, event) - if (allow_close === false) return + let allow_close = this.callback(button, result, event); + if (allow_close === false) return; } - this.hide() - this.delete() + this.hide(); + this.delete(); } build(): this { - let options = this.configuration + let options = this.configuration; - let results: Record + let results: Record; if (options.translateKey) { - if (!options.title) options.title = tl('message.' + options.translateKey + '.title') + if (!options.title) options.title = tl('message.' + options.translateKey + '.title'); if (!options.message) - options.message = tl('message.' + options.translateKey + '.message') + options.message = tl('message.' + options.translateKey + '.message'); } - let content = Interface.createElement('div', { class: 'dialog_content' }) + let content = Interface.createElement('div', { class: 'dialog_content' }); this.object = Interface.createElement( 'dialog', { class: 'dialog', style: 'width: auto;', id: 'message_box' }, @@ -980,8 +980,8 @@ export class MessageBox extends Dialog { ), content, ] - ) - let jq_dialog = $(this.object) + ); + let jq_dialog = $(this.object); if (options.message) { content.append( @@ -990,55 +990,55 @@ export class MessageBox extends Dialog { pureMarked(tl(options.message)) + '' )[0] - ) + ); } if (options.icon) { - let bar = jq_dialog.find('.dialog_bar') - bar.prepend($(Blockbench.getIconNode(options.icon)).addClass('message_box_icon')) - bar.append('
    ') + let bar = jq_dialog.find('.dialog_bar'); + bar.prepend($(Blockbench.getIconNode(options.icon)).addClass('message_box_icon')); + bar.append('
    '); } if (options.commands) { - let list = Interface.createElement('ul') + let list = Interface.createElement('ul'); for (let id in options.commands) { - let command = options.commands[id] + let command = options.commands[id]; if (!command || (typeof command == 'object' && !Condition(command.condition))) - continue - let text = tl(typeof command == 'string' ? command : command.text) + continue; + let text = tl(typeof command == 'string' ? command : command.text); let entry = Interface.createElement( 'li', { class: 'dialog_message_box_command' }, text - ) + ); if (typeof command == 'object') { if (command.icon) { - entry.prepend(Blockbench.getIconNode(command.icon)) + entry.prepend(Blockbench.getIconNode(command.icon)); } if (command.description) { - let label = Interface.createElement('label', {}, tl(command.description)) - entry.append(label) + let label = Interface.createElement('label', {}, tl(command.description)); + entry.append(label); } } entry.addEventListener('click', e => { - this.close(id, results, e) - }) - list.append(entry) + this.close(id, results, e); + }); + list.append(entry); } - content.append(list) + content.append(list); } if (options.checkboxes) { - let list = Interface.createElement('ul', { class: 'dialog_message_box_checkboxes' }) - results = {} + let list = Interface.createElement('ul', { class: 'dialog_message_box_checkboxes' }); + results = {}; for (let id in options.checkboxes) { - let checkbox = options.checkboxes[id] - results[id] = false + let checkbox = options.checkboxes[id]; + results[id] = false; if (typeof checkbox == 'object') { - results[id] = !!checkbox.value - if (!Condition(checkbox.condition)) continue + results[id] = !!checkbox.value; + if (!Condition(checkbox.condition)) continue; } - let text = tl(typeof checkbox == 'string' ? checkbox : checkbox.text) + let text = tl(typeof checkbox == 'string' ? checkbox : checkbox.text); let entry = Interface.createElement( 'li', { class: 'dialog_message_box_checkbox' }, @@ -1053,179 +1053,179 @@ export class MessageBox extends Dialog { text ), ] - ) + ); entry.firstElementChild.addEventListener('change', e => { - results[id] = (e.target as HTMLInputElement).checked - }) - list.append(entry) + results[id] = (e.target as HTMLInputElement).checked; + }); + list.append(entry); } - content.append(list) + content.append(list); } // Buttons if (this.buttons.length) { - let buttons = [] + let buttons = []; this.buttons.forEach((b, i) => { - let btn = Interface.createElement('button', { type: 'button' }, tl(b)) - buttons.push(btn) + let btn = Interface.createElement('button', { type: 'button' }, tl(b)); + buttons.push(btn); btn.addEventListener('click', event => { - this.close(i, results, event) - }) - }) - buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn') - buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn') - let button_bar = $('
    ') + this.close(i, results, event); + }); + }); + buttons[this.confirmIndex] && buttons[this.confirmIndex].classList.add('confirm_btn'); + buttons[this.cancelIndex] && buttons[this.cancelIndex].classList.add('cancel_btn'); + let button_bar = $('
    '); buttons.forEach((button, i) => { - button_bar.append(button) - }) + button_bar.append(button); + }); - jq_dialog.append(button_bar[0]) + jq_dialog.append(button_bar[0]); } //Draggable if (this.draggable !== false) { - jq_dialog.addClass('draggable') + jq_dialog.addClass('draggable'); // @ts-ignore jq_dialog.draggable({ handle: '.dialog_handle', containment: '#page_wrapper', - }) - this.object.style.position = 'absolute' + }); + this.object.style.position = 'absolute'; } - let x = (window.innerWidth - 540) / 2 - this.object.style.left = x + 'px' - this.object.style.position = 'absolute' + let x = (window.innerWidth - 540) / 2; + this.object.style.left = x + 'px'; + this.object.style.position = 'absolute'; this.object.style.top = - limitNumber(window.innerHeight / 2 - jq_dialog.height() / 2 - 140, 0, 2000) + 'px' + limitNumber(window.innerHeight / 2 - jq_dialog.height() / 2 - 140, 0, 2000) + 'px'; if (options.width) { - this.object.style.width = options.width + 'px' + this.object.style.width = options.width + 'px'; } else { this.object.style.width = limitNumber((options.buttons ? options.buttons.length : 1) * 170 + 44, 380, 894) + - 'px' + 'px'; } - return this + return this; } delete() { - if (this.object) this.object.remove() - this.object = null + if (this.object) this.object.remove(); + this.object = null; } } interface ConfigDialogOptions extends DialogOptions {} export class ConfigDialog extends Dialog { constructor(id: string, options: ConfigDialogOptions) { - super(id, options) + super(id, options); } show(anchor: HTMLElement) { - super.show() - $('#blackout').hide() + super.show(); + $('#blackout').hide(); if (anchor instanceof HTMLElement) { - let anchor_position = $(anchor).offset() - this.object.style.top = anchor_position.top + anchor.offsetHeight + 'px' + let anchor_position = $(anchor).offset(); + this.object.style.top = anchor_position.top + anchor.offsetHeight + 'px'; this.object.style.left = Math.clamp( anchor_position.left - 30, 0, window.innerWidth - this.object.clientWidth - (this.title ? 0 : 30) - ) + 'px' + ) + 'px'; } - return this + return this; } build() { - if (this.object) this.object.remove() - this.object = document.createElement('dialog') - this.object.className = 'dialog config_dialog' + if (this.object) this.object.remove(); + this.object = document.createElement('dialog'); + this.object.className = 'dialog config_dialog'; - this.max_label_width = 140 - this.uses_wide_inputs = false + this.max_label_width = 140; + this.uses_wide_inputs = false; - let title_bar + let title_bar; if (this.title) { title_bar = Interface.createElement( 'div', { class: 'config_dialog_title' }, tl(this.title) - ) - this.object.append(title_bar) + ); + this.object.append(title_bar); } - let wrapper = document.createElement('div') - wrapper.className = 'dialog_wrapper' + let wrapper = document.createElement('div'); + wrapper.className = 'dialog_wrapper'; - let content = document.createElement('content') - content.className = 'dialog_content' - this.object.append(wrapper) + let content = document.createElement('content'); + content.className = 'dialog_content'; + this.object.append(wrapper); - wrapper.append(content) + wrapper.append(content); - this.form = new InputForm(this.form_config) - content.append(this.form.node) - this.max_label_width = Math.max(this.max_label_width, this.form.max_label_width) - if (this.form.uses_wide_inputs) this.uses_wide_inputs = true + this.form = new InputForm(this.form_config); + content.append(this.form.node); + this.max_label_width = Math.max(this.max_label_width, this.form.max_label_width); + if (this.form.uses_wide_inputs) this.uses_wide_inputs = true; this.form.on('change', ({ result }) => { if (this.configuration) { for (let key in result) { - this.configuration[key] = result[key] + this.configuration[key] = result[key]; } } - if (this.onFormChange) this.onFormChange(result) - }) + if (this.onFormChange) this.onFormChange(result); + }); if (this.toolbars) { - buildToolbars(this) + buildToolbars(this); } - let close_button = document.createElement('div') - close_button.classList.add('dialog_close_button') - close_button.innerHTML = 'clear' + let close_button = document.createElement('div'); + close_button.classList.add('dialog_close_button'); + close_button.innerHTML = 'clear'; close_button.addEventListener('click', e => { - this.cancel() - }) + this.cancel(); + }); if (title_bar) { - title_bar.append(close_button) + title_bar.append(close_button); } else { - this.object.append(close_button) + this.object.append(close_button); } if (typeof this.onBuild == 'function') { - this.onBuild(this.object) + this.onBuild(this.object); } - return this + return this; } delete() { - if (this.object) this.object.remove() - this.object = null + if (this.object) this.object.remove(); + this.object = null; } } export class ToolConfig extends ConfigDialog { declare options: { - [key: string]: FormResultValue - } + [key: string]: FormResultValue; + }; constructor(id: string, options: ConfigDialogOptions) { - super(id, options) + super(id, options); - this.options = {} - let config_saved_data: Record + this.options = {}; + let config_saved_data: Record; try { - let stored = localStorage.getItem(`tool_config.${this.id}`) - config_saved_data = JSON.parse(stored) - if (!config_saved_data) config_saved_data = {} + let stored = localStorage.getItem(`tool_config.${this.id}`); + config_saved_data = JSON.parse(stored); + if (!config_saved_data) config_saved_data = {}; } catch (err) { - config_saved_data = {} + config_saved_data = {}; } for (let key in options.form) { - if (options.form[key] == '_') continue + if (options.form[key] == '_') continue; if (key == 'enabled' && BarItem.constructing instanceof Toggle) { - this.options[key] = BarItem.constructing.value - continue + this.options[key] = BarItem.constructing.value; + continue; } this.options[key] = - config_saved_data[key] ?? InputForm.getDefaultValue(options.form[key]) + config_saved_data[key] ?? InputForm.getDefaultValue(options.form[key]); } } /** @@ -1233,16 +1233,16 @@ export class ToolConfig extends ConfigDialog { * @param anchor Optional element to anchor the menu to */ show(anchor?: HTMLElement): this { - super.show(anchor) - this.setFormValues(this.options, false) - return this + super.show(anchor); + this.setFormValues(this.options, false); + return this; } /** * Save any changes in local storage */ save() { - localStorage.setItem(`tool_config.${this.id}`, JSON.stringify(this.options)) - return this + localStorage.setItem(`tool_config.${this.id}`, JSON.stringify(this.options)); + return this; } /** * Change and save a number of options in the config @@ -1250,17 +1250,17 @@ export class ToolConfig extends ConfigDialog { */ changeOptions(options: Record): this { for (let key in options) { - this.options[key] = options[key] + this.options[key] = options[key]; } if (this.form) { - this.form.setValues(options) + this.form.setValues(options); } - this.save() - return this + this.save(); + return this; } close() { - this.save() - this.hide() + this.save(); + this.hide(); } } @@ -1271,4 +1271,4 @@ Object.assign(window, { MessageBox, ConfigDialog, ToolConfig, -}) +}); diff --git a/js/interface/form.ts b/js/interface/form.ts index d0fc923b5..0fdb6035f 100644 --- a/js/interface/form.ts +++ b/js/interface/form.ts @@ -1,17 +1,17 @@ -import { Blockbench } from '../api' -import { Clipbench } from '../copy_paste' -import { Filesystem } from '../file_system' -import { tl } from '../languages' -import { EventSystem } from '../util/event_system' -import { getStringWidth, pureMarked } from '../util/util' -import { Interface } from './interface' +import { Blockbench } from '../api'; +import { Clipbench } from '../copy_paste'; +import { Filesystem } from '../file_system'; +import { tl } from '../languages'; +import { EventSystem } from '../util/event_system'; +import { getStringWidth, pureMarked } from '../util/util'; +import { Interface } from './interface'; -type ReadType = 'buffer' | 'binary' | 'text' | 'image' +type ReadType = 'buffer' | 'binary' | 'text' | 'image'; interface FileResult { - name: string - path: string - content: string | ArrayBuffer - no_file?: boolean + name: string; + path: string; + content: string | ArrayBuffer; + no_file?: boolean; } export enum FormInputType { Text = 'text', @@ -35,461 +35,464 @@ export enum FormInputType { } export interface FormElementOptions { - label?: string + label?: string; /** * Detailed description of the field, available behind the questionmark icon or on mouse hover */ - description?: string + description?: string; /** * Type of the input. If unspecified, defaults to text */ - type?: FormInputType | `${FormInputType}` + type?: FormInputType | `${FormInputType}`; /** * Stretch the input field across the whole width of the form */ - full_width?: boolean + full_width?: boolean; /** Set the input to read-only */ - readonly?: boolean + readonly?: boolean; /** Add buttons to allow copying and sharing the text or link */ - share_text?: boolean + share_text?: boolean; /** * The default value */ - value?: any + value?: any; /** * The default selected option for 'select', 'inline_select' and 'radio' types. Alias for 'value' */ - default?: any - placeholder?: string - list?: string[] + default?: any; + placeholder?: string; + list?: string[]; /** * When using 'text' type, the text to display. Markdown is supported */ - text?: string - condition?: ConditionResolvable + text?: string; + condition?: ConditionResolvable; /** * When using 'range' type, allow users to modify the numeric input */ - editable_range_label?: boolean - colorpicker?: any + editable_range_label?: boolean; + colorpicker?: any; /** * On numeric inputs, the minimum possible value */ - min?: number + min?: number; /** * On numeric inputs, the maximum possible value */ - max?: number + max?: number; /** * The step in which the value can be increased / decreased */ - step?: number + step?: number; /** * On num slider inputs, The color of the slider */ - color?: string + color?: string; /** * If enabled, the value is forced to multiples of the "step" value. This can be used to create integer-only inputs etc. */ - force_step?: boolean + force_step?: boolean; /** * The number of dimensions when using a vector type */ - dimensions?: number + dimensions?: number; /** * The height of the input on textareas, in pixels */ - height?: number + height?: number; /** * Available options on select or inline_select inputs */ options?: | Record - | (() => Record) + | (() => Record); /** * List of buttons for the button type */ - buttons?: string[] + buttons?: string[]; /** * Function to get the interval value for a num_slider based on the input event * @returns Interval value */ - getInterval?: (event: Event) => number + getInterval?: (event: Event) => number; /** * For num_sliders, the sliding interval mode */ - interval_type?: 'position' | 'rotation' + interval_type?: 'position' | 'rotation'; /** * Allow users to toggle the entire option on or off */ - toggle_enabled?: boolean + toggle_enabled?: boolean; /** * Set whether the setting is toggled on or off by default. Requires 'toggle_enabled' field to be set to true */ - toggle_default?: boolean + toggle_default?: boolean; /** * Lock the ratio of a vector */ - linked_ratio?: boolean + linked_ratio?: boolean; /** * Set the return type of files on file inputs */ - return_as?: 'file' + return_as?: 'file'; /** * Runs when any of the buttons is pressed * @param button_index Index of the clicked button in the buttons list */ - click?: (button_index: number) => void + click?: (button_index: number) => void; - readtype?: ReadType | ((file: string) => ReadType) - resource_id?: string - extensions?: string[] - filetype?: string + readtype?: ReadType | ((file: string) => ReadType); + resource_id?: string; + extensions?: string[]; + filetype?: string; } -export type FormResultValue = string | number | boolean | any[] | {} +export type FormResultValue = string | number | boolean | any[] | {}; export type InputFormConfig = { - [formElement: string]: '_' | FormElementOptions -} -type FormValues = Record + [formElement: string]: '_' | FormElementOptions; +}; +type FormValues = Record; // MARK: InputForm export class InputForm extends EventSystem { - uuid: string - form_config: InputFormConfig - form_data: { [formElement: string]: FormElement } - node: HTMLDivElement - max_label_width: number - uses_wide_inputs: boolean + uuid: string; + form_config: InputFormConfig; + form_data: { [formElement: string]: FormElement }; + node: HTMLDivElement; + max_label_width: number; + uses_wide_inputs: boolean; constructor(form_config: InputFormConfig, options = {}) { - super() - this.uuid = guid() - this.form_config = form_config - this.form_data = {} - this.node = Interface.createElement('div', { class: 'form' }) as HTMLDivElement - this.max_label_width = 0 - this.uses_wide_inputs = false + super(); + this.uuid = guid(); + this.form_config = form_config; + this.form_data = {}; + this.node = Interface.createElement('div', { class: 'form' }) as HTMLDivElement; + this.max_label_width = 0; + this.uses_wide_inputs = false; - this.buildForm() - this.updateValues({ cause: 'setup' }) + this.buildForm(); + this.updateValues({ cause: 'setup' }); } buildForm() { - let jq_node = $(this.node) - jq_node.empty() + let jq_node = $(this.node); + jq_node.empty(); for (let form_id in this.form_config) { - let input_config = this.form_config[form_id] - form_id = form_id.replace(/"/g, '') + let input_config = this.form_config[form_id]; + form_id = form_id.replace(/"/g, ''); if (input_config === '_') { - jq_node.append('
    ') - continue + jq_node.append('
    '); + continue; } - let InputType = FormElement.types[input_config.type] ?? FormElement.types.text + let InputType = FormElement.types[input_config.type] ?? FormElement.types.text; let form_element = (this.form_data[form_id] = new InputType( form_id, input_config, this - )) + )); let bar = Interface.createElement('div', { class: `dialog_bar bar form_bar form_bar_${form_id}`, - }) - form_element.build(bar) - form_element.setup() - if (form_element.uses_wide_inputs) this.uses_wide_inputs = true - jq_node.append(bar) + }); + form_element.build(bar); + form_element.setup(); + if (form_element.uses_wide_inputs) this.uses_wide_inputs = true; + jq_node.append(bar); } - this.updateLabelWidth() + this.updateLabelWidth(); } updateLabelWidth(ignore_hidden: boolean = false) { - this.max_label_width = 0 + this.max_label_width = 0; for (let form_id in this.form_config) { - form_id = form_id.replace(/"/g, '') - let form_element = this.form_data[form_id] - if (!form_element || (ignore_hidden && !Condition(form_element.condition))) continue + form_id = form_id.replace(/"/g, ''); + let form_element = this.form_data[form_id]; + if (!form_element || (ignore_hidden && !Condition(form_element.condition))) continue; - if (form_element.uses_wide_inputs) this.uses_wide_inputs = true - this.max_label_width = Math.max(form_element.label_width, this.max_label_width) + if (form_element.uses_wide_inputs) this.uses_wide_inputs = true; + this.max_label_width = Math.max(form_element.label_width, this.max_label_width); } - this.node.style.setProperty('--max_label_width', this.max_label_width + 'px') + this.node.style.setProperty('--max_label_width', this.max_label_width + 'px'); } update(form_result: FormValues) { for (let form_id in this.form_config) { - let form_element = this.form_data[form_id] - let input_config = this.form_config[form_id] + let form_element = this.form_data[form_id]; + let input_config = this.form_config[form_id]; if (typeof input_config == 'object' && form_element.bar) { - let show = Condition(input_config.condition, form_result) - form_element.bar.style.display = show ? null : 'none' + let show = Condition(input_config.condition, form_result); + form_element.bar.style.display = show ? null : 'none'; } } } updateValues(context: { cause?: string; changed_keys?: string[] } = {}) { - let form_result = this.getResult() - this.update(form_result) + let form_result = this.getResult(); + this.update(form_result); if (context.cause != 'setup') { this.dispatchEvent('change', { result: form_result, cause: context.cause, changed_keys: context.changed_keys, - }) + }); } if (context.cause == 'input') { - this.dispatchEvent('input', { result: form_result, changed_keys: context.changed_keys }) + this.dispatchEvent('input', { + result: form_result, + changed_keys: context.changed_keys, + }); } - return form_result + return form_result; } setValues(values: FormValues, update = true) { for (let form_id in this.form_config) { - let form_element = this.form_data[form_id] - let input_config = this.form_config[form_id] + let form_element = this.form_data[form_id]; + let input_config = this.form_config[form_id]; if ( form_element && 'setValue' in form_element && values[form_id] != undefined && typeof input_config == 'object' ) { - form_element.setValue(values[form_id]) + form_element.setValue(values[form_id]); } } - if (update) this.updateValues({ cause: 'update_value' }) + if (update) this.updateValues({ cause: 'update_value' }); } setToggles(values: Record, update = true) { for (let form_id in this.form_config) { - let input_config = this.form_config[form_id] - let form_element = this.form_data[form_id] + let input_config = this.form_config[form_id]; + let form_element = this.form_data[form_id]; if ( values[form_id] != undefined && typeof input_config == 'object' && form_element.input_toggle && form_element.bar ) { - form_element.input_toggle.checked = values[form_id] + form_element.input_toggle.checked = values[form_id]; form_element.bar.classList.toggle( 'form_toggle_disabled', !form_element.input_toggle.checked - ) + ); } } - if (update) this.updateValues({ cause: 'update_toggle' }) + if (update) this.updateValues({ cause: 'update_toggle' }); } getResult(): FormValues { - let result = {} + let result = {}; for (let form_id in this.form_data) { - let form_element = this.form_data[form_id] + let form_element = this.form_data[form_id]; if (form_element) { if (form_element.input_toggle && form_element.input_toggle.checked == false) { - result[form_id] = null - continue + result[form_id] = null; + continue; } else { - result[form_id] = form_element.getValue() + result[form_id] = form_element.getValue(); } } } - return result + return result; } static getDefaultValue(input_config: FormElementOptions): FormResultValue { - let set_value = input_config.value ?? input_config.default - if (set_value) return set_value - let type = FormElement.types[input_config.type] + let set_value = input_config.value ?? input_config.default; + if (set_value) return set_value; + let type = FormElement.types[input_config.type]; if (type) { - return type.prototype.getDefault.call({ options: input_config }) + return type.prototype.getDefault.call({ options: input_config }); } - return '' + return ''; } } // MARK: FormElement export class FormElement extends EventSystem { - id: string - form: InputForm - condition: ConditionResolvable - options: FormElementOptions - bar: HTMLElement - input_toggle?: HTMLInputElement - label_width: number + id: string; + form: InputForm; + condition: ConditionResolvable; + options: FormElementOptions; + bar: HTMLElement; + input_toggle?: HTMLInputElement; + label_width: number; constructor(id: string, options: FormElementOptions, form: InputForm) { - super() - this.id = id - this.options = options - this.form = form - this.condition = options.condition + super(); + this.id = id; + this.options = options; + this.form = form; + this.condition = options.condition; } build(bar: HTMLDivElement) { - this.bar = bar + this.bar = bar; if (typeof this.options.label == 'string') { let label = Interface.createElement( 'label', { class: 'name_space_left', for: this.id }, tl(this.options.label) - ) - bar.append(label) + ); + bar.append(label); if ( !this.options.full_width && this.condition !== false /*Weed out inputs where the condition is always false*/ ) { - this.label_width = getStringWidth(label.textContent) + this.label_width = getStringWidth(label.textContent); } } if (this.options.full_width) { - bar.classList.add('full_width_dialog_bar') + bar.classList.add('full_width_dialog_bar'); } if (this.options.description) { - bar.setAttribute('title', tl(this.options.description)) + bar.setAttribute('title', tl(this.options.description)); } } get uses_wide_inputs() { - return this.options.full_width + return this.options.full_width; } getValue(): any { - return this.getDefault() + return this.getDefault(); } setValue(value: any) {} getDefault(): any { - return null + return null; } change() { - this.dispatchEvent('change', { changed_keys: [this.id] }) - this.form.updateValues({ cause: 'input', changed_keys: [this.id] }) + this.dispatchEvent('change', { changed_keys: [this.id] }); + this.form.updateValues({ cause: 'input', changed_keys: [this.id] }); } setup() { if (this.options.readonly && 'input' in this) { - $(this.input).attr('readonly', 'readonly').removeClass('focusable_input') + $(this.input).attr('readonly', 'readonly').removeClass('focusable_input'); } if (this.options.description) { - let icon = document.createElement('i') - icon.className = 'fa fa-question dialog_form_description' + let icon = document.createElement('i'); + icon.className = 'fa fa-question dialog_form_description'; icon.onclick = () => { - Blockbench.showQuickMessage(this.options.description, 3600) - } - this.bar.append(icon) + Blockbench.showQuickMessage(this.options.description, 3600); + }; + this.bar.append(icon); } if (this.options.toggle_enabled) { let toggle = Interface.createElement('input', { type: 'checkbox', class: 'focusable_input form_input_toggle', id: this.id + '_toggle', - }) as HTMLInputElement - toggle.checked = this.options.toggle_default != false - this.bar.append(toggle) - this.bar.classList.toggle('form_toggle_disabled', !toggle.checked) + }) as HTMLInputElement; + toggle.checked = this.options.toggle_default != false; + this.bar.append(toggle); + this.bar.classList.toggle('form_toggle_disabled', !toggle.checked); toggle.addEventListener('input', () => { - this.change() - this.bar.classList.toggle('form_toggle_disabled', !toggle.checked) - }) - this.input_toggle = toggle + this.change(); + this.bar.classList.toggle('form_toggle_disabled', !toggle.checked); + }); + this.input_toggle = toggle; } } - static types: Record = {} + static types: Record = {}; static registerType(id: string, type_class: typeof FormElement) { - FormElement.types[id] = type_class + FormElement.types[id] = type_class; } } // MARK: FormElement Types FormElement.types.range = class FormElementRange extends FormElement { - input: HTMLInputElement - numeric_input?: { value: number } + input: HTMLInputElement; + numeric_input?: { value: number }; build(bar: HTMLDivElement) { - super.build(bar) - let scope = this + super.build(bar); + let scope = this; let input_element = $(``) - bar.append(input_element[0]) - this.input = input_element[0] as HTMLInputElement + value="${this.options.value || 0}" min="${this.options.min}" max="${this.options.max}" step="${this.options.step || 1}">`); + bar.append(input_element[0]); + this.input = input_element[0] as HTMLInputElement; if (!this.options.editable_range_label) { let display = Interface.createElement( 'span', { class: 'range_input_label' }, (this.options.value || 0).toString() - ) - bar.append(display) + ); + bar.append(display); input_element.on('input', () => { - let result = this.form.getResult() - display.textContent = trimFloatNumber(result[this.id] as number) - }) + let result = this.form.getResult(); + display.textContent = trimFloatNumber(result[this.id] as number); + }); } else { - bar.classList.add('slider_input_combo') + bar.classList.add('slider_input_combo'); let numeric_input = new Interface.CustomElements.NumericInput(this.id + '_number', { value: this.options.value ?? 0, min: this.options.min, max: this.options.max, step: this.options.step, onChange() { - input_element.val(numeric_input.value) - scope.change() + input_element.val(numeric_input.value); + scope.change(); }, - }) - this.numeric_input = numeric_input - bar.append(numeric_input.node) + }); + this.numeric_input = numeric_input; + bar.append(numeric_input.node); input_element.on('input', () => { - let result = parseFloat(input_element.val() as string) - numeric_input.value = result - }) + let result = parseFloat(input_element.val() as string); + numeric_input.value = result; + }); } input_element.on('input', () => { - this.change() - }) + this.change(); + }); } getValue(): number { - let result: number + let result: number; if (this.options.editable_range_label && this.numeric_input) { - result = this.numeric_input.value + result = this.numeric_input.value; } else { - result = parseFloat(this.input.value) + result = parseFloat(this.input.value); } if (this.options.force_step && this.options.step) { - result = Math.round(result / this.options.step) * this.options.step + result = Math.round(result / this.options.step) * this.options.step; } - return Math.clamp(result || 0, this.options.min, this.options.max) + return Math.clamp(result || 0, this.options.min, this.options.max); } setValue(value: number): void { - this.input.value = value.toString() + this.input.value = value.toString(); } getDefault(): number { - return Math.clamp(0, this.options.min, this.options.max) + return Math.clamp(0, this.options.min, this.options.max); } -} +}; FormElement.types.info = class FormElementInfo extends FormElement { get uses_wide_inputs(): boolean { - return true + return true; } build(bar: HTMLDivElement) { - this.bar = bar + this.bar = bar; if (typeof this.options.label == 'string') { let label = Interface.createElement( 'label', { class: 'name_space_left', for: this.id }, tl(this.options.label) - ) - bar.append(label) + ); + bar.append(label); if ( !this.options.full_width && this.condition !== false /*Weed out inputs where the condition is always false*/ ) { - this.label_width = getStringWidth(label.textContent) + this.label_width = getStringWidth(label.textContent); } } if (this.options.full_width) { - bar.classList.add('full_width_dialog_bar') + bar.classList.add('full_width_dialog_bar'); } if (this.options.description) { - bar.setAttribute('title', tl(this.options.description)) + bar.setAttribute('title', tl(this.options.description)); } - let content = Interface.createElement('div', { class: 'small_text' }) - content.innerHTML = pureMarked(tl(this.options.text)) - bar.append(content) + let content = Interface.createElement('div', { class: 'small_text' }); + content.innerHTML = pureMarked(tl(this.options.text)); + bar.append(content); } -} +}; FormElement.types.text = class FormElementText extends FormElement { - password_toggle?: HTMLElement - input: HTMLInputElement + password_toggle?: HTMLElement; + input: HTMLInputElement; build(bar: HTMLDivElement) { - super.build(bar) - let scope = this + super.build(bar); + let scope = this; let input_element = Object.assign(document.createElement('input'), { type: 'text', className: 'dark_bordered half focusable_input', @@ -497,349 +500,349 @@ FormElement.types.text = class FormElementText extends FormElement { value: this.options.value || '', placeholder: this.options.placeholder || '', oninput() { - scope.change() + scope.change(); }, - }) - this.input = input_element - bar.append(input_element) + }); + this.input = input_element; + bar.append(input_element); if (this.options.list) { - let list_id = `${this.form.uuid}_${this.id}_list` - input_element.setAttribute('list', list_id) - let list = Interface.createElement('datalist', { id: list_id }) + let list_id = `${this.form.uuid}_${this.id}_list`; + input_element.setAttribute('list', list_id); + let list = Interface.createElement('datalist', { id: list_id }); for (let value of this.options.list) { - let node = document.createElement('option') - node.value = value - list.append(node) + let node = document.createElement('option'); + node.value = value; + list.append(node); } - bar.append(list[0]) + bar.append(list[0]); } if (this.options.type == 'password') { bar.append(`
    -
    `) - input_element.type = 'password' - let hidden = true - let this_bar = $(bar) - let this_input_element = input_element + `); + input_element.type = 'password'; + let hidden = true; + let this_bar = $(bar); + let this_input_element = input_element; this_bar.find('.password_toggle').on('click', e => { - hidden = !hidden - this_input_element.setAttribute('type', hidden ? 'password' : 'text') + hidden = !hidden; + this_input_element.setAttribute('type', hidden ? 'password' : 'text'); this_bar.find('.password_toggle i')[0].className = hidden ? 'fas fa-eye-slash' - : 'fas fa-eye' - }) + : 'fas fa-eye'; + }); } if (this.options.share_text && this.options.value) { - let text = this.options.value.toString() - let is_url = text.startsWith('https://') + let text = this.options.value.toString(); + let is_url = text.startsWith('https://'); let copy_button = Interface.createElement( 'div', { class: 'form_input_tool tool', title: tl('dialog.copy_to_clipboard') }, Blockbench.getIconNode('content_paste') - ) + ); copy_button.addEventListener('click', e => { if (isApp || navigator.clipboard) { - Clipbench.setText(text) - Blockbench.showQuickMessage('dialog.copied_to_clipboard') - input_element.focus() - document.execCommand('selectAll') + Clipbench.setText(text); + Blockbench.showQuickMessage('dialog.copied_to_clipboard'); + input_element.focus(); + document.execCommand('selectAll'); } else if (is_url) { Blockbench.showMessageBox({ title: 'dialog.share_model.title', message: `[${text}](${text})`, - }) + }); } - }) - bar.append(copy_button) + }); + bar.append(copy_button); if (is_url) { let open_button = Interface.createElement( 'div', { class: 'form_input_tool tool', title: tl('dialog.open_url') }, Blockbench.getIconNode('open_in_browser') - ) + ); open_button.addEventListener('click', e => { - Blockbench.openLink(text) - }) - bar.append(open_button) + Blockbench.openLink(text); + }); + bar.append(open_button); } if (navigator.share) { let share_button = Interface.createElement( 'div', { class: 'form_input_tool tool', title: tl('generic.share') }, Blockbench.getIconNode('share') - ) + ); share_button.addEventListener('click', e => { navigator.share({ title: this.options.label ? tl(this.options.label) : 'Share', [is_url ? 'url' : 'text']: text, - }) - }) - bar.append(share_button) + }); + }); + bar.append(share_button); } } } getValue(): string { - return this.input.value + return this.input.value; } setValue(value: string) { - this.input.value = value + this.input.value = value; } getDefault(): string { - return '' + return ''; } -} +}; FormElement.types.textarea = class FormElementTextarea extends FormElement { - textarea: HTMLTextAreaElement + textarea: HTMLTextAreaElement; build(bar: HTMLDivElement) { - super.build(bar) - let scope = this + super.build(bar); + let scope = this; this.textarea = Object.assign(document.createElement('textarea'), { className: 'focusable_input', id: this.id, value: this.options.value || '', placeholder: this.options.placeholder || '', oninput() { - scope.change() + scope.change(); }, - }) - this.textarea.style.height = (this.options.height || 150) + 'px' - bar.append(this.textarea) + }); + this.textarea.style.height = (this.options.height || 150) + 'px'; + bar.append(this.textarea); } getValue() { - return this.textarea.value + return this.textarea.value; } setValue(value: string) { - this.textarea.value = value + this.textarea.value = value; } getDefault(): string { - return '' + return ''; } -} +}; FormElement.types.number = class FormElementNumber extends FormElement { - numeric_input: { value: number; node: HTMLElement } + numeric_input: { value: number; node: HTMLElement }; build(bar: HTMLDivElement) { - super.build(bar) - let scope = this + super.build(bar); + let scope = this; this.numeric_input = new Interface.CustomElements.NumericInput(this.id, { value: this.options.value, min: this.options.min, max: this.options.max, step: this.options.step, onChange() { - scope.change() + scope.change(); }, - }) - bar.append(this.numeric_input.node) + }); + bar.append(this.numeric_input.node); } getValue() { - let result = Math.clamp(this.numeric_input.value, this.options.min, this.options.max) + let result = Math.clamp(this.numeric_input.value, this.options.min, this.options.max); if (this.options.force_step && this.options.step) { - result = Math.round(result / this.options.step) * this.options.step + result = Math.round(result / this.options.step) * this.options.step; } - return result + return result; } setValue(value: number) { - this.numeric_input.value = value + this.numeric_input.value = value; } getDefault(): number { - return Math.clamp(0, this.options.min, this.options.max) + return Math.clamp(0, this.options.min, this.options.max); } -} +}; FormElement.types.select = class FormElementSelect extends FormElement { - select_input: { node: HTMLElement; set: (value: string) => void } + select_input: { node: HTMLElement; set: (value: string) => void }; build(bar: HTMLDivElement) { - super.build(bar) - let scope = this + super.build(bar); + let scope = this; this.select_input = new Interface.CustomElements.SelectInput(this.id, { options: this.options.options, value: this.options.value || this.options.default, onInput() { - scope.change() + scope.change(); }, - }) - bar.append(this.select_input.node) + }); + bar.append(this.select_input.node); } getValue(): string { - return this.select_input.node.getAttribute('value') + return this.select_input.node.getAttribute('value'); } setValue(value: string) { - this.select_input.set(value) + this.select_input.set(value); } getDefault(): string { - return Object.keys(this.options.options)[0] ?? '' + return Object.keys(this.options.options)[0] ?? ''; } -} +}; FormElement.types.inline_select = class FormElementInlineSelect extends FormElement { build(bar: HTMLDivElement) { - super.build(bar) - let options = [] - let val = this.options.value || this.options.default - let i = 0 + super.build(bar); + let options = []; + let val = this.options.value || this.options.default; + let i = 0; for (let key in this.options.options) { - let is_selected = val ? key == val : i == 0 + let is_selected = val ? key == val : i == 0; let text: string = typeof this.options.options[key] == 'string' ? this.options.options[key] - : this.options.options[key].name + : this.options.options[key].name; let node = Interface.createElement( 'li', { class: is_selected ? 'selected' : '', key: key }, tl(text) - ) + ); node.onclick = () => { options.forEach(li => { - li.classList.toggle('selected', li == node) - }) - this.change() - } - options.push(node) - i++ + li.classList.toggle('selected', li == node); + }); + this.change(); + }; + options.push(node); + i++; } - let wrapper = Interface.createElement('ul', { class: 'form_inline_select' }, options) - bar.append(wrapper) + let wrapper = Interface.createElement('ul', { class: 'form_inline_select' }, options); + bar.append(wrapper); } getValue(): string { - return $(this.bar).find('li.selected')[0]?.getAttribute('key') || '' + return $(this.bar).find('li.selected')[0]?.getAttribute('key') || ''; } setValue(value: string) { $(this.bar) .find('li') .each((i, el) => { - el.classList.toggle('selected', el.getAttribute('key') == value) - }) + el.classList.toggle('selected', el.getAttribute('key') == value); + }); } getDefault(): string { - return Object.keys(this.options.options)[0] ?? '' + return Object.keys(this.options.options)[0] ?? ''; } -} +}; FormElement.types.inline_multi_select = class FormElementInlineMultiSelect extends FormElement { - value: Record + value: Record; build(bar: HTMLDivElement) { - super.build(bar) - let val = this.options.value || this.options.default - this.value = {} + super.build(bar); + let val = this.options.value || this.options.default; + this.value = {}; if (val) { for (let key in this.options.options) { - this.value[key] = !!val[key] + this.value[key] = !!val[key]; } } - let i = 0 - let options = [] + let i = 0; + let options = []; for (let key in this.options.options) { - let is_selected = val && val[key] - let text = this.options.options[key] - if (typeof text != 'string') text = text.name + let is_selected = val && val[key]; + let text = this.options.options[key]; + if (typeof text != 'string') text = text.name; let node = Interface.createElement( 'li', { class: is_selected ? 'selected' : '', key: key }, tl(text) - ) + ); node.onclick = () => { - this.value[key] = !this.value[key] - node.classList.toggle('selected', this.value[key]) - this.change() - } - options.push(node) - i++ + this.value[key] = !this.value[key]; + node.classList.toggle('selected', this.value[key]); + this.change(); + }; + options.push(node); + i++; } let wrapper = Interface.createElement( 'ul', { class: 'form_inline_select multi_select' }, options - ) - bar.append(wrapper) + ); + bar.append(wrapper); } getValue(): Record { - return this.value + return this.value; } setValue(value: Record) { for (let key in value) { if (this.value[key] !== undefined) { - this.value[key] = value[key] + this.value[key] = value[key]; } } $(this.bar) .find('li') .each((i, el) => { - el.classList.toggle('selected', !!this.value[el.getAttribute('key')]) - }) + el.classList.toggle('selected', !!this.value[el.getAttribute('key')]); + }); } getDefault(): Record { - return {} + return {}; } -} +}; FormElement.types.radio = class FormElementRadio extends FormElement { - input: HTMLInputElement + input: HTMLInputElement; build(bar: HTMLDivElement) { - super.build(bar) - let el = $(`
    `) + super.build(bar); + let el = $(`
    `); for (let key in this.options.options) { let name = tl( typeof this.options.options[key] == 'string' ? this.options.options[key] : this.options.options[key].name - ) + ); el.append(`
    -
    `) - this.input = el.find(`input#${key}`)[0] as HTMLInputElement + `); + this.input = el.find(`input#${key}`)[0] as HTMLInputElement; this.input.addEventListener('change', args => { - this.change() - }) + this.change(); + }); } - bar.append(el[0]) + bar.append(el[0]); } getValue(): string { return $(this.bar) .find('.form_part_radio#' + this.id + ' input:checked') - .attr('id') + .attr('id'); } setValue(value: string) { $(this.bar) .find('.form_part_radio input#' + value) - .prop('checked', true) + .prop('checked', true); } getDefault() { - return Object.keys(this.options.options)[0] ?? '' + return Object.keys(this.options.options)[0] ?? ''; } -} +}; FormElement.types.buttons = class FormElementButtons extends FormElement { get uses_wide_inputs(): boolean { - return true + return true; } build(bar: HTMLDivElement) { - this.bar = bar - let list = document.createElement('div') - list.className = 'dialog_form_buttons' + this.bar = bar; + let list = document.createElement('div'); + list.className = 'dialog_form_buttons'; this.options.buttons.forEach((button_text, index) => { - let button = document.createElement('a') - button.innerText = tl(button_text) + let button = document.createElement('a'); + button.innerText = tl(button_text); button.addEventListener('click', e => { - this.options.click(index) - }) - list.append(button) - }) - bar.append(list) + this.options.click(index); + }); + list.append(button); + }); + bar.append(list); } -} +}; FormElement.types.num_slider = class FormElementNumSlider extends FormElement { - slider: NumSlider + slider: NumSlider; build(bar: HTMLDivElement) { - super.build(bar) - let getInterval = this.options.getInterval + super.build(bar); + let getInterval = this.options.getInterval; // @ts-ignore - if (this.options.interval_type == 'position') getInterval = getSpatialInterval + if (this.options.interval_type == 'position') getInterval = getSpatialInterval; // @ts-ignore - if (this.options.interval_type == 'rotation') getInterval = getRotationInterval + if (this.options.interval_type == 'rotation') getInterval = getRotationInterval; this.slider = new NumSlider('form_slider_' + this.id, { private: true, // @ts-ignore onChange: () => { - this.change() + this.change(); }, color: this.options.color || '', getInterval, @@ -849,49 +852,49 @@ FormElement.types.num_slider = class FormElementNumSlider extends FormElement { max: this.options.max, step: this.options.step || 1, }, - }) - bar.append(this.slider.node) - this.slider.update() + }); + bar.append(this.slider.node); + this.slider.update(); } getValue(): number { - return this.slider.get() + return this.slider.get(); } setValue(value: number) { - this.slider.setValue(value) + this.slider.setValue(value); } getDefault() { - return Math.clamp(0, this.options.min, this.options.max) + return Math.clamp(0, this.options.min, this.options.max); } -} +}; FormElement.types.vector = class FormElementVector extends FormElement { - linked_ratio: boolean + linked_ratio: boolean; constructor(id: string, options: FormElementOptions, form: InputForm) { - super(id, options, form) - this.linked_ratio = false + super(id, options, form); + this.linked_ratio = false; } build(bar: HTMLDivElement) { - super.build(bar) - let scope = this - let group = $(`
    `) - bar.append(group[0]) - let vector_inputs = [] + super.build(bar); + let scope = this; + let group = $(`
    `); + bar.append(group[0]); + let vector_inputs = []; let initial_value = - this.options.value instanceof Array ? this.options.value.slice() : [1, 1, 1] + this.options.value instanceof Array ? this.options.value.slice() : [1, 1, 1]; let updateInputs = changed_input => { - let i2 = -1 + let i2 = -1; for (let vector_input_2 of vector_inputs) { - i2++ - if (vector_input_2 == changed_input) continue + i2++; + if (vector_input_2 == changed_input) continue; let new_value = initial_value[i2] * - (changed_input.value / initial_value[vector_inputs.indexOf(changed_input)]) - new_value = Math.clamp(new_value, this.options.min, this.options.max) + (changed_input.value / initial_value[vector_inputs.indexOf(changed_input)]); + new_value = Math.clamp(new_value, this.options.min, this.options.max); if (this.options.force_step && this.options.step) { - new_value = Math.round(new_value / this.options.step) * this.options.step + new_value = Math.round(new_value / this.options.step) * this.options.step; } - vector_input_2.value = new_value + vector_input_2.value = new_value; } - } + }; for (let i = 0; i < (this.options.dimensions || 3); i++) { let numeric_input = new Interface.CustomElements.NumericInput(this.id + '_' + i, { value: this.options.value ? this.options.value[i] : 0, @@ -900,69 +903,69 @@ FormElement.types.vector = class FormElementVector extends FormElement { step: this.options.step, onChange() { if (scope.linked_ratio) { - updateInputs(numeric_input) + updateInputs(numeric_input); } - scope.change() + scope.change(); }, - }) - group.append(numeric_input.node) - vector_inputs.push(numeric_input) + }); + group.append(numeric_input.node); + vector_inputs.push(numeric_input); } if (typeof this.options.linked_ratio == 'boolean') { let updateState = () => { - icon.textContent = scope.linked_ratio ? 'link' : 'link_off' - linked_ratio_toggle.classList.toggle('enabled', scope.linked_ratio) - } - this.linked_ratio = this.options.linked_ratio - let icon = Blockbench.getIconNode('link') + icon.textContent = scope.linked_ratio ? 'link' : 'link_off'; + linked_ratio_toggle.classList.toggle('enabled', scope.linked_ratio); + }; + this.linked_ratio = this.options.linked_ratio; + let icon = Blockbench.getIconNode('link'); let linked_ratio_toggle = Interface.createElement( 'div', { class: 'tool linked_ratio_toggle' }, icon - ) + ); linked_ratio_toggle.addEventListener('click', event => { - this.linked_ratio = !this.linked_ratio + this.linked_ratio = !this.linked_ratio; if (this.linked_ratio) { - initial_value = vector_inputs.map(v => v.value) + initial_value = vector_inputs.map(v => v.value); // updateInputs(vector_inputs[0]); // scope.change() } - updateState() - }) - updateState() - group.append(linked_ratio_toggle) + updateState(); + }); + updateState(); + group.append(linked_ratio_toggle); } } getValue(): number[] { - let result: number[] = [] + let result: number[] = []; for (let i = 0; i < (this.options.dimensions || 3); i++) { - let input_value = $(this.bar).find(`input#${this.id}_${i}`).val() as string - let num = Math.clamp(parseFloat(input_value) || 0, this.options.min, this.options.max) + let input_value = $(this.bar).find(`input#${this.id}_${i}`).val() as string; + let num = Math.clamp(parseFloat(input_value) || 0, this.options.min, this.options.max); if (this.options.force_step && this.options.step) { - num = Math.round(num / this.options.step) * this.options.step + num = Math.round(num / this.options.step) * this.options.step; } - result.push(num) + result.push(num); } - return result + return result; } setValue(value: number[]) { for (let i = 0; i < (this.options.dimensions || 3); i++) { - $(this.bar).find(`input#${this.id}_${i}`).val(value[i]) + $(this.bar).find(`input#${this.id}_${i}`).val(value[i]); } } getDefault(): number[] { return new Array(this.options.dimensions ?? 3).fill( Math.clamp(0, this.options.min, this.options.max) - ) + ); } -} +}; FormElement.types.color = class FormElementColor extends FormElement { - colorpicker: ColorPicker + colorpicker: ColorPicker; build(bar: HTMLDivElement) { - super.build(bar) - if (this.options.colorpicker) this.colorpicker = this.options.colorpicker - let scope = this + super.build(bar); + if (this.options.colorpicker) this.colorpicker = this.options.colorpicker; + let scope = this; if (!this.colorpicker) { this.colorpicker = new ColorPicker({ id: 'cp_' + this.id, @@ -971,107 +974,109 @@ FormElement.types.color = class FormElementColor extends FormElement { label: false, private: true, value: this.options.value, - }) + }); } // @ts-ignore this.colorpicker.onChange = function () { - scope.change() - } + scope.change(); + }; this.colorpicker.on('modify_color', () => { - scope.change() - }) - bar.append(this.colorpicker.getNode()) + scope.change(); + }); + bar.append(this.colorpicker.getNode()); } getValue(): tinycolor.Instance { - return this.colorpicker.get() + return this.colorpicker.get(); } setValue(value: string | any) { - this.colorpicker.set(value) + this.colorpicker.set(value); } getDefault() { - return '#ffffff' + return '#ffffff'; } -} +}; FormElement.types.checkbox = class FormElementCheckbox extends FormElement { - input: HTMLInputElement + input: HTMLInputElement; build(bar: HTMLDivElement) { - super.build(bar) + super.build(bar); this.input = Interface.createElement('input', { type: 'checkbox', class: 'focusable_input', id: this.id, - }) - this.input.checked = this.options.value - bar.append(this.input) + }); + this.input.checked = this.options.value; + bar.append(this.input); this.input.addEventListener('change', () => { - this.change() - }) + this.change(); + }); } setValue(value: boolean) { - this.input.checked = value + this.input.checked = value; } getValue(): boolean { - return this.input.checked + return this.input.checked; } getDefault() { - return false + return false; } -} +}; class FormElementFile extends FormElement { - file: Filesystem.FileResult - value: string - content: any - input: HTMLInputElement + file: Filesystem.FileResult; + value: string; + content: any; + input: HTMLInputElement; build(bar: HTMLDivElement) { - super.build(bar) - if (this.options.type == 'folder' && !isApp) return - this.value = this.options.value - let scope = this + super.build(bar); + if (this.options.type == 'folder' && !isApp) return; + this.value = this.options.value; + let scope = this; let input = $( `` - ) - this.input = input[0] as HTMLInputElement + ); + this.input = input[0] as HTMLInputElement; this.input.value = settings.streamer_mode.value ? `[${tl('generic.redacted')}]` - : this.value || '' - let input_wrapper = $('
    ') - input_wrapper.append(input) - bar.append(input_wrapper[0]) - bar.classList.add('form_bar_file') + : this.value || ''; + let input_wrapper = $('
    '); + input_wrapper.append(input); + bar.append(input_wrapper[0]); + bar.classList.add('form_bar_file'); switch (this.options.type) { case 'file': - input_wrapper.append('insert_drive_file') - break + input_wrapper.append('insert_drive_file'); + break; case 'folder': - input_wrapper.append('folder') - break + input_wrapper.append('folder'); + break; case 'save': - input_wrapper.append('save') - break + input_wrapper.append('save'); + break; } let remove_button = $( '
    clear
    ' - ) - bar.append(remove_button[0]) + ); + bar.append(remove_button[0]); remove_button.on('click', e => { - e.stopPropagation() - this.value = '' - delete this.content - delete this.file - input.val('') - }) + e.stopPropagation(); + this.value = ''; + delete this.content; + delete this.file; + input.val(''); + }); input_wrapper.on('click', e => { const fileCB = files => { - this.value = files[0].path - this.content = files[0].content - this.file = files[0] - input.val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value) - scope.change() - } + this.value = files[0].path; + this.content = files[0].content; + this.file = files[0]; + input.val( + settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value + ); + scope.change(); + }; switch (this.options.type) { case 'file': Blockbench.import( @@ -1083,14 +1088,14 @@ class FormElementFile extends FormElement { readtype: this.options.readtype, }, fileCB - ) - break + ); + break; case 'folder': let path = Blockbench.pickDirectory({ startpath: this.value, - }) - if (path) fileCB([{ path }]) - break + }); + if (path) fileCB([{ path }]); + break; case 'save': Blockbench.export( { @@ -1101,44 +1106,46 @@ class FormElementFile extends FormElement { custom_writer: () => {}, }, path => { - this.value = path + this.value = path; input.val( settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value - ) - scope.change() + ); + scope.change(); } - ) - break + ); + break; } - }) + }); } getValue(): Filesystem.FileResult | string | any { if (this.options.return_as == 'file') { - return this.file + return this.file; } else { - return isApp ? this.value : this.content + return isApp ? this.value : this.content; } } setValue(value: string) { - delete this.file + delete this.file; if (this.options.return_as == 'file' && typeof value == 'object') { - this.file = value - this.value = this.file.name + this.file = value; + this.value = this.file.name; } else if (isApp) { - this.value = value + this.value = value; } else { - this.content = value + this.content = value; } - $(this.input).val(settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value) + $(this.input).val( + settings.streamer_mode.value ? `[${tl('generic.redacted')}]` : this.value + ); } getDefault() { - return '' + return ''; } } -FormElement.types.file = FormElementFile -FormElement.types.folder = FormElementFile -FormElement.types.save = FormElementFile +FormElement.types.file = FormElementFile; +FormElement.types.folder = FormElementFile; +FormElement.types.save = FormElementFile; -Object.assign(window, { InputForm, FormElement }) +Object.assign(window, { InputForm, FormElement }); diff --git a/js/interface/panels.ts b/js/interface/panels.ts index ce5f7c32b..9655570e3 100644 --- a/js/interface/panels.ts +++ b/js/interface/panels.ts @@ -1,74 +1,74 @@ -import { Prop } from '../misc' -import { EventSystem } from '../util/event_system' -import { InputForm } from './form' +import { Prop } from '../misc'; +import { EventSystem } from '../util/event_system'; +import { InputForm } from './form'; import { Interface, Panels, openTouchKeyboardModifierMenu, resizeWindow, updateInterface, -} from './interface' -import { Toolbar } from './toolbars' -import { Vue } from '../lib/libs' -import { Blockbench } from '../api' +} from './interface'; +import { Toolbar } from './toolbars'; +import { Vue } from '../lib/libs'; +import { Blockbench } from '../api'; interface PanelPositionData { - slot: PanelSlot - float_position: [number, number] - float_size: [number, number] - height: number - folded: boolean - attached_to?: string - attached_index?: number - fixed_height: boolean + slot: PanelSlot; + float_position: [number, number]; + float_size: [number, number]; + height: number; + folded: boolean; + attached_to?: string; + attached_index?: number; + fixed_height: boolean; } -type PanelSlot = 'left_bar' | 'right_bar' | 'top' | 'bottom' | 'float' | 'hidden' +type PanelSlot = 'left_bar' | 'right_bar' | 'top' | 'bottom' | 'float' | 'hidden'; interface PanelOptions { - id?: string - name?: string - icon: string - optional?: boolean - plugin?: string - min_height?: number - menu?: any + id?: string; + name?: string; + icon: string; + optional?: boolean; + plugin?: string; + min_height?: number; + menu?: any; /** * If true, the panel can automatically become smaller or larger than its initial size in the sidebar */ - growable?: boolean + growable?: boolean; /** * When true, the height of the panel can be adjusted in the sidebar */ - resizable?: true - selection_only?: boolean - condition?: ConditionResolvable - display_condition?: ConditionResolvable + resizable?: true; + selection_only?: boolean; + condition?: ConditionResolvable; + display_condition?: ConditionResolvable; /** * Adds a button to the panel that allows users to pop-out and expand the panel on click */ - expand_button?: boolean + expand_button?: boolean; toolbars?: | { - [id: string]: Toolbar + [id: string]: Toolbar; } - | Toolbar[] - default_position?: Partial | number - component?: Vue.Component - form?: InputForm - default_side?: 'right' | 'left' + | Toolbar[]; + default_position?: Partial | number; + component?: Vue.Component; + form?: InputForm; + default_side?: 'right' | 'left'; /** * Identifier of another panel to insert this one above */ - insert_before?: string + insert_before?: string; /** * Identifier of another panel to insert this one below */ - insert_after?: string - onResize?(): void - onFold?(): void + insert_after?: string; + onResize?(): void; + onFold?(): void; } -type PanelEvent = 'drag' | 'fold' | 'change_zindex' | 'move_to' | 'moved_to' | 'update' +type PanelEvent = 'drag' | 'fold' | 'change_zindex' | 'move_to' | 'moved_to' | 'update'; const DEFAULT_POSITION_DATA: PanelPositionData = { slot: 'left_bar', @@ -79,110 +79,110 @@ const DEFAULT_POSITION_DATA: PanelPositionData = { fixed_height: false, attached_to: '', attached_index: undefined, -} +}; export class Panel extends EventSystem { - type: 'panel' - id: string - name: string - icon: string - menu: Menu - condition: ConditionResolvable - display_condition: ConditionResolvable - resizable?: boolean - growable: boolean - min_height?: number - optional: boolean - plugin?: string - onResize: () => void - onFold: () => void - - default_position: PanelPositionData - previous_slot: PanelSlot - width: number - height: number - - open_attached_panel: Panel - - node: HTMLElement - container: HTMLElement - handle: HTMLElement - tab_bar: HTMLElement - form?: InputForm - vue?: Vue - inside_vue?: Vue - toolbars: Toolbar[] - sidebar_resize_handle: HTMLElement - resize_handles?: HTMLElement + type: 'panel'; + id: string; + name: string; + icon: string; + menu: Menu; + condition: ConditionResolvable; + display_condition: ConditionResolvable; + resizable?: boolean; + growable: boolean; + min_height?: number; + optional: boolean; + plugin?: string; + onResize: () => void; + onFold: () => void; + + default_position: PanelPositionData; + previous_slot: PanelSlot; + width: number; + height: number; + + open_attached_panel: Panel; + + node: HTMLElement; + container: HTMLElement; + handle: HTMLElement; + tab_bar: HTMLElement; + form?: InputForm; + vue?: Vue; + inside_vue?: Vue; + toolbars: Toolbar[]; + sidebar_resize_handle: HTMLElement; + resize_handles?: HTMLElement; constructor(id: string | PanelOptions, data: PanelOptions) { - super() - if (!data && typeof id != 'string') data = id - let scope = this - this.type = 'panel' - this.id = typeof id == 'string' ? id : data.id || 'new_panel' - this.name = tl(data.name ? data.name : `panel.${this.id}`) - this.icon = data.icon - this.menu = data.menu - this.condition = data.condition - this.display_condition = data.display_condition - this.previous_slot = 'left_bar' - this.optional = data.optional ?? true + super(); + if (!data && typeof id != 'string') data = id; + let scope = this; + this.type = 'panel'; + this.id = typeof id == 'string' ? id : data.id || 'new_panel'; + this.name = tl(data.name ? data.name : `panel.${this.id}`); + this.icon = data.icon; + this.menu = data.menu; + this.condition = data.condition; + this.display_condition = data.display_condition; + this.previous_slot = 'left_bar'; + this.optional = data.optional ?? true; // @ts-ignore "Plugins" is loaded after so it cannot be imported this.plugin = - data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : '') + data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : ''); - this.growable = data.growable - this.resizable = data.resizable - this.min_height = data.min_height ?? 60 + this.growable = data.growable; + this.resizable = data.resizable; + this.min_height = data.min_height ?? 60; - this.onResize = data.onResize - this.onFold = data.onFold - this.events = {} - this.toolbars = [] - this.open_attached_panel = this + this.onResize = data.onResize; + this.onFold = data.onFold; + this.events = {}; + this.toolbars = []; + this.open_attached_panel = this; - if (!Interface.data.panels[this.id]) Interface.data.panels[this.id] = {} - if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {} - this.default_position = data.default_position || ({} as any) + if (!Interface.data.panels[this.id]) Interface.data.panels[this.id] = {}; + if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {}; + this.default_position = data.default_position || ({} as any); if (this.default_position && this.default_position.slot) - this.previous_slot = this.default_position.slot + this.previous_slot = this.default_position.slot; this.updatePositionData({ slot: (data.default_side ? data.default_side + '_bar' : 'left_bar') as PanelSlot, - }) + }); for (let mode_id in Interface.data.modes) { - let mode_data = Interface.getModeData(mode_id) + let mode_data = Interface.getModeData(mode_id); if (!mode_data.panels[this.id]) - mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)) + mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)); } this.handle = Interface.createElement( 'div', { class: 'panel_handle', panel_id: this.id }, Interface.createElement('span', {}, this.name) - ) - this.node = Interface.createElement('div', { class: 'panel', id: `panel_${this.id}` }) + ); + this.node = Interface.createElement('div', { class: 'panel', id: `panel_${this.id}` }); this.tab_bar = Interface.createElement('div', { class: 'panel_tab_bar' }, [ Interface.createElement('div', { class: 'panel_tab_list' }, this.handle), - ]) + ]); this.container = Interface.createElement( 'div', { class: 'panel_container', panel_id: this.id }, [this.tab_bar, this.node] - ) + ); this.handle.addEventListener('mousedown', (event: MouseEvent) => { if (this.attached_to) { - Panels[this.attached_to].selectTab(this) + Panels[this.attached_to].selectTab(this); } else { - this.selectTab() + this.selectTab(); } - }) + }); if (this.growable) { - this.container.classList.add('grow') - this.node.classList.add('grow') + this.container.classList.add('grow'); + this.node.classList.add('grow'); } // Toolbars @@ -191,54 +191,54 @@ export class Panel extends EventSystem { ? data.toolbars : data.toolbars ? Object.keys(data.toolbars) - : [] + : []; for (let item of toolbars) { - let toolbar = item instanceof Toolbar ? item : this.toolbars[item] - if (toolbar instanceof Toolbar == false) continue + let toolbar = item instanceof Toolbar ? item : this.toolbars[item]; + if (toolbar instanceof Toolbar == false) continue; if (toolbar.label) { let label = Interface.createElement( 'p', { class: 'panel_toolbar_label' }, tl(toolbar.name) - ) - this.node.append(label) - toolbar.label_node = label + ); + this.node.append(label); + toolbar.label_node = label; } - this.node.append(toolbar.node) - this.toolbars.push(toolbar) + this.node.append(toolbar.node); + this.toolbars.push(toolbar); } if (data.form) { - this.form = data.form instanceof InputForm ? data.form : new InputForm(data.form) - ;(this.node.append(this.form.node), this.form.buildForm()) + this.form = data.form instanceof InputForm ? data.form : new InputForm(data.form); + (this.node.append(this.form.node), this.form.buildForm()); } if (data.component) { - let component_mount = Interface.createElement('div') - this.node.append(component_mount) - let onmounted = data.component.mounted + let component_mount = Interface.createElement('div'); + this.node.append(component_mount); + let onmounted = data.component.mounted; data.component.mounted = function () { Vue.nextTick(() => { - let toolbar_wrappers = this.$el.querySelectorAll('.toolbar_wrapper') + let toolbar_wrappers = this.$el.querySelectorAll('.toolbar_wrapper'); toolbar_wrappers.forEach((wrapper: HTMLElement) => { - let id = wrapper.getAttribute('toolbar') - let toolbar = scope.toolbars.find(toolbar => toolbar.id == id) + let id = wrapper.getAttribute('toolbar'); + let toolbar = scope.toolbars.find(toolbar => toolbar.id == id); if (toolbar) { - wrapper.append(toolbar.node) + wrapper.append(toolbar.node); } - }) + }); if (typeof onmounted == 'function') { - onmounted.call(this) + onmounted.call(this); } //updateInterfacePanels() - }) - } - this.vue = this.inside_vue = new Vue(data.component) - this.vue.$mount(component_mount) - this.vue.$el.classList.add('panel_vue_wrapper') + }); + }; + this.vue = this.inside_vue = new Vue(data.component); + this.vue.$mount(component_mount); + this.vue.$el.classList.add('panel_vue_wrapper'); } if (!Blockbench.isMobile) { @@ -247,168 +247,168 @@ export class Panel extends EventSystem { 'div', { class: 'tool panel_control panel_expanding_button' }, Blockbench.getIconNode('fullscreen') - ) - this.tab_bar.append(expand_button) + ); + this.tab_bar.append(expand_button); expand_button.addEventListener('click', e => { if (this.slot == 'float') { - this.moveTo(this.previous_slot) + this.moveTo(this.previous_slot); } else { - this.moveTo('float') - this.moveToFront() + this.moveTo('float'); + this.moveToFront(); } - }) + }); } let menu_button = Interface.createElement( 'div', { class: 'light_on_hover panel_menu_button' }, Blockbench.getIconNode('more_vert') - ) - this.handle.append(menu_button) + ); + this.handle.append(menu_button); menu_button.addEventListener('click', e => { - this.snap_menu.open(menu_button, this) - }) + this.snap_menu.open(menu_button, this); + }); let fold_button = Interface.createElement( 'div', { class: 'tool panel_control panel_folding_button' }, Blockbench.getIconNode('expand_more') - ) - this.tab_bar.append(fold_button) + ); + this.tab_bar.append(fold_button); fold_button.addEventListener('click', e => { - this.fold() - }) + this.fold(); + }); this.tab_bar.firstElementChild.addEventListener('dblclick', e => { - this.fold() - }) + this.fold(); + }); if (this.resizable) { this.sidebar_resize_handle = Interface.createElement('div', { class: 'panel_sidebar_resize_handle', - }) - this.container.append(this.sidebar_resize_handle) + }); + this.container.append(this.sidebar_resize_handle); let resize = (e1: MouseEvent | TouchEvent) => { - e1 = convertTouchEvent(e1) - let height_before = this.container.clientHeight - let started = false - let direction = this.container.classList.contains('bottommost_panel') ? -1 : 1 - let other_panel_height_before = {} + e1 = convertTouchEvent(e1); + let height_before = this.container.clientHeight; + let started = false; + let direction = this.container.classList.contains('bottommost_panel') ? -1 : 1; + let other_panel_height_before = {}; let other_panels = this.slot == 'right_bar' ? Interface.getRightPanels() - : Interface.getLeftPanels() + : Interface.getLeftPanels(); - e1.preventDefault() + e1.preventDefault(); let drag = (e2: MouseEvent | TouchEvent) => { - e2 = convertTouchEvent(e2) + e2 = convertTouchEvent(e2); if ( !started && Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2) > 12 ) { - started = true - this.sidebar_resize_handle.classList.add('dragging') + started = true; + this.sidebar_resize_handle.classList.add('dragging'); } - if (!started) return + if (!started) return; - let change_amount = (e2.clientY - e1.clientY) * direction - let sidebar_gap = this.container.parentElement.clientHeight + let change_amount = (e2.clientY - e1.clientY) * direction; + let sidebar_gap = this.container.parentElement.clientHeight; for (let panel of other_panels) { - sidebar_gap -= panel.container.clientHeight + sidebar_gap -= panel.container.clientHeight; } - let height1 = this.position_data.height - this.position_data.fixed_height = true + let height1 = this.position_data.height; + this.position_data.fixed_height = true; this.position_data.height = Math.max( height_before + change_amount, this.min_height - ) - this.update() - let height_difference = this.position_data.height - height1 + ); + this.update(); + let height_difference = this.position_data.height - height1; let panel_b = other_panels.find( p => p != this && p.resizable && p.min_height < (p.height ?? p.container.clientHeight) - ) + ); if (sidebar_gap < 1 && panel_b && change_amount > 0) { if (!other_panel_height_before[panel_b.id]) other_panel_height_before[panel_b.id] = - panel_b.height ?? panel_b.container.clientHeight - panel_b.position_data.fixed_height = true + panel_b.height ?? panel_b.container.clientHeight; + panel_b.position_data.fixed_height = true; panel_b.position_data.height = Math.max( panel_b.position_data.height - height_difference, this.min_height - ) - panel_b.update() + ); + panel_b.update(); } - } + }; let stop = e2 => { - convertTouchEvent(e2) - - removeEventListeners(document, 'mousemove touchmove', drag) - removeEventListeners(document, 'mouseup touchend', stop) - this.sidebar_resize_handle.classList.remove('dragging') - } - addEventListeners(document, 'mousemove touchmove', drag) - addEventListeners(document, 'mouseup touchend', stop) - } + convertTouchEvent(e2); + + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + this.sidebar_resize_handle.classList.remove('dragging'); + }; + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); + }; addEventListeners(this.sidebar_resize_handle, 'mousedown touchstart', event => resize(event as MouseEvent) - ) + ); } let getHostPanelUnderCursor: (event: MouseEvent) => Panel | undefined = event => { for (let panel_id in Panels) { - let panel: Panel = Panels[panel_id] + let panel: Panel = Panels[panel_id]; if (panel != this && panel.container.isConnected) { - let bounding_box = panel.tab_bar.getBoundingClientRect() + let bounding_box = panel.tab_bar.getBoundingClientRect(); if ( Math.isBetween(event.clientX, bounding_box.left, bounding_box.right) && Math.isBetween(event.clientY, bounding_box.top, bounding_box.bottom) ) { - return panel + return panel; } } } - } + }; addEventListeners(this.handle, 'mousedown touchstart', (e1: MouseEvent) => { if ( e1.target instanceof HTMLElement && e1.target.classList.contains('panel_menu_button') ) - return - if (e1.which == 2 || e1.which == 3) return - convertTouchEvent(e1) - let started = false + return; + if (e1.which == 2 || e1.which == 3) return; + convertTouchEvent(e1); + let started = false; let position_before = this.slot == 'float' ? this.position_data.float_position.slice() - : [e1.clientX - e1.offsetX, e1.clientY - e1.offsetY - 55] - let original_show_left_bar = Prop.show_left_bar - let original_show_right_bar = Prop.show_right_bar - - let target_slot: PanelSlot | undefined - let target_panel: Panel | null - let target_before = false - let attach_to = false - let move_attached_panels = e1.shiftKey || Pressing.overrides.shift + : [e1.clientX - e1.offsetX, e1.clientY - e1.offsetY - 55]; + let original_show_left_bar = Prop.show_left_bar; + let original_show_right_bar = Prop.show_right_bar; + + let target_slot: PanelSlot | undefined; + let target_panel: Panel | null; + let target_before = false; + let attach_to = false; + let move_attached_panels = e1.shiftKey || Pressing.overrides.shift; function updateTargetHighlight(event: MouseEvent) { - $(`.panel_container[order], .panel_handle[order]`).attr('order', null) - $(`.panel_container.attach_target`).removeClass('attach_target') + $(`.panel_container[order], .panel_handle[order]`).attr('order', null); + $(`.panel_container.attach_target`).removeClass('attach_target'); if (attach_to && target_panel) { - target_panel.container.classList.add('attach_target') - let panel_container_offset = $(target_panel.container).offset()?.left ?? 0 + target_panel.container.classList.add('attach_target'); + let panel_container_offset = $(target_panel.container).offset()?.left ?? 0; let attached_panels = [target_panel].concat( target_panel.getAttachedPanels() - ) + ); let target_handle_panel = attached_panels.findLast(handle_panel => { return ( @@ -416,119 +416,119 @@ export class Panel extends EventSystem { panel_container_offset + handle_panel.handle.offsetLeft + handle_panel.handle.clientWidth - ) - }) ?? attached_panels[0] + ); + }) ?? attached_panels[0]; if (target_handle_panel) { - target_handle_panel.handle.setAttribute('order', '1') + target_handle_panel.handle.setAttribute('order', '1'); } } else if (target_panel) { target_panel.container.setAttribute( 'order', (target_before ? -1 : 1).toString() - ) + ); } if (target_slot) { - Interface.center_screen.setAttribute('snapside', target_slot) + Interface.center_screen.setAttribute('snapside', target_slot); } else { - Interface.center_screen.removeAttribute('snapside') + Interface.center_screen.removeAttribute('snapside'); } if ( (target_slot == 'right_bar' && Interface.right_bar_width) || (target_slot == 'left_bar' && Interface.left_bar_width) ) { - Interface.center_screen.removeAttribute('snapside') + Interface.center_screen.removeAttribute('snapside'); } - Interface.left_bar.classList.toggle('drop_target', target_slot == 'left_bar') - Interface.right_bar.classList.toggle('drop_target', target_slot == 'right_bar') + Interface.left_bar.classList.toggle('drop_target', target_slot == 'left_bar'); + Interface.right_bar.classList.toggle('drop_target', target_slot == 'right_bar'); if (target_slot == 'left_bar' && !Prop.show_left_bar) - Interface.toggleSidebar('left') + Interface.toggleSidebar('left'); if (target_slot == 'right_bar' && !Prop.show_right_bar) - Interface.toggleSidebar('right') + Interface.toggleSidebar('right'); if (target_slot != 'left_bar' && Prop.show_left_bar && !original_show_left_bar) - Interface.toggleSidebar('left') + Interface.toggleSidebar('left'); if ( target_slot != 'right_bar' && Prop.show_right_bar && !original_show_right_bar ) - Interface.toggleSidebar('right') + Interface.toggleSidebar('right'); } let drag = e2 => { - convertTouchEvent(e2) + convertTouchEvent(e2); if ( !started && Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2) > 15 ) { - started = true - let attached_panels = this.getAttachedPanels() + started = true; + let attached_panels = this.getAttachedPanels(); if (attached_panels.length && !move_attached_panels) { - let first = attached_panels.splice(0, 1)[0] - first.moveTo(this.slot, this) + let first = attached_panels.splice(0, 1)[0]; + first.moveTo(this.slot, this); for (let other of attached_panels) { - first.attachPanel(other) + first.attachPanel(other); } } if (this.slot !== 'float' || this.attached_to) { - this.moveTo('float') - this.moveToFront() + this.moveTo('float'); + this.moveToFront(); } - this.node.classList.add('dragging') + this.node.classList.add('dragging'); Interface.addSuggestedModifierKey( 'ctrl', 'modifier_actions.move_panel_without_docking' - ) + ); } - if (!started) return + if (!started) return; this.position_data.float_position[0] = - position_before[0] + e2.clientX - e1.clientX + position_before[0] + e2.clientX - e1.clientX; this.position_data.float_position[1] = - position_before[1] + e2.clientY - e1.clientY + position_before[1] + e2.clientY - e1.clientY; - let threshold = 40 - let threshold_y = 64 - let host_target_panel - target_slot = null - target_panel = null - target_before = false - attach_to = false + let threshold = 40; + let threshold_y = 64; + let host_target_panel; + target_slot = null; + target_panel = null; + target_before = false; + attach_to = false; if (e2.ctrlOrCmd) { } else if ((host_target_panel = getHostPanelUnderCursor(e2))) { - target_panel = host_target_panel - attach_to = true - target_slot = undefined + target_panel = host_target_panel; + attach_to = true; + target_slot = undefined; } else if (e2.clientX < Math.max(Interface.left_bar_width, threshold)) { - target_slot = 'left_bar' + target_slot = 'left_bar'; for (let child of Interface.left_bar.childNodes) { - if (!child.clientHeight) continue - let y = $(child).offset()?.top - if (!y) continue - target_panel = Panels[child.getAttribute('panel_id')] + if (!child.clientHeight) continue; + let y = $(child).offset()?.top; + if (!y) continue; + target_panel = Panels[child.getAttribute('panel_id')]; if (e2.clientY < y + child.clientHeight / 2) { - target_before = true - break + target_before = true; + break; } } } else if ( e2.clientX > document.body.clientWidth - Math.max(Interface.right_bar_width, threshold) ) { - target_slot = 'right_bar' + target_slot = 'right_bar'; for (let child of Interface.right_bar.childNodes) { - if (!child.clientHeight) continue - let y = $(child).offset()?.top - if (!y) continue - target_panel = Panels[child.getAttribute('panel_id')] + if (!child.clientHeight) continue; + let y = $(child).offset()?.top; + if (!y) continue; + target_panel = Panels[child.getAttribute('panel_id')]; if (e2.clientY < y + child.clientHeight / 2) { - target_before = true - break + target_before = true; + break; } } } else if ( @@ -536,7 +536,7 @@ export class Panel extends EventSystem { e2.clientX > Interface.left_bar_width && e2.clientX < Interface.work_screen.clientWidth - Interface.right_bar_width ) { - target_slot = 'top' + target_slot = 'top'; } else if ( e2.clientY > Interface.work_screen.offsetTop + @@ -546,284 +546,284 @@ export class Panel extends EventSystem { e2.clientX > Interface.left_bar_width && e2.clientX < Interface.work_screen.clientWidth - Interface.right_bar_width ) { - target_slot = 'bottom' + target_slot = 'bottom'; } - updateTargetHighlight(e2) - this.update(true) + updateTargetHighlight(e2); + this.update(true); this.dispatchEvent('drag', { event: e2, target_before, attach_to, target_panel, target_slot, - }) - } + }); + }; let stop = e2 => { - convertTouchEvent(e2) - this.node.classList.remove('dragging') - Interface.center_screen.removeAttribute('snapside') - $(`.panel_container[order], .panel_handle[order]`).attr('order', null) - Interface.left_bar.classList.remove('drop_target') - Interface.right_bar.classList.remove('drop_target') - $(`.panel_container.attach_target`).removeClass('attach_target') + convertTouchEvent(e2); + this.node.classList.remove('dragging'); + Interface.center_screen.removeAttribute('snapside'); + $(`.panel_container[order], .panel_handle[order]`).attr('order', null); + Interface.left_bar.classList.remove('drop_target'); + Interface.right_bar.classList.remove('drop_target'); + $(`.panel_container.attach_target`).removeClass('attach_target'); Interface.removeSuggestedModifierKey( 'ctrl', 'modifier_actions.move_panel_without_docking' - ) + ); if (attach_to) { - this.fixed_height = false - target_panel.attachPanel(this) + this.fixed_height = false; + target_panel.attachPanel(this); } else if (target_slot) { - this.fixed_height = false - this.moveTo(target_slot, target_panel, target_before) + this.fixed_height = false; + this.moveTo(target_slot, target_panel, target_before); } if (this.slot != 'float') { - this.position_data.float_position[0] = position_before[0] - this.position_data.float_position[1] = position_before[1] + this.position_data.float_position[0] = position_before[0]; + this.position_data.float_position[1] = position_before[1]; } - this.update() - updateInterface() - - removeEventListeners(document, 'mousemove touchmove', drag) - removeEventListeners(document, 'mouseup touchend', stop) - } - addEventListeners(document, 'mousemove touchmove', drag) - addEventListeners(document, 'mouseup touchend', stop) - }) + this.update(); + updateInterface(); + + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + }; + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); + }); } else { let close_button = Interface.createElement( 'div', { class: 'tool panel_control' }, Blockbench.getIconNode('clear') - ) - this.tab_bar.append(close_button) + ); + this.tab_bar.append(close_button); close_button.addEventListener('click', e => { - Interface.PanelSelectorVue.select(null) - }) - this.tab_bar.classList.add('single_tab') + Interface.PanelSelectorVue.select(null); + }); + this.tab_bar.classList.add('single_tab'); addEventListeners( this.handle.firstElementChild as HTMLElement, 'mousedown touchstart', (e1: MouseEvent) => { - convertTouchEvent(e1) - let started = false - let height_before = this.position_data.height + convertTouchEvent(e1); + let started = false; + let height_before = this.position_data.height; let max = Blockbench.isLandscape ? window.innerWidth - 50 - : Interface.work_screen.clientHeight + : Interface.work_screen.clientHeight; let drag = e2 => { - convertTouchEvent(e2) + convertTouchEvent(e2); let diff = Blockbench.isLandscape ? e1.clientX - e2.clientX - : e1.clientY - e2.clientY + : e1.clientY - e2.clientY; if (!started && Math.abs(diff) > 4) { - started = true - if (this.folded) this.fold() + started = true; + if (this.folded) this.fold(); } - if (!started) return + if (!started) return; let sign = Blockbench.isLandscape && settings.mobile_panel_side.value == 'left' ? -1 - : 1 + : 1; this.position_data.height = Math.clamp( height_before + diff * sign, 140, max - ) + ); - this.update(true) - resizeWindow() - } + this.update(true); + resizeWindow(); + }; let stop = e2 => { - convertTouchEvent(e2) + convertTouchEvent(e2); - this.update() + this.update(); - removeEventListeners(document, 'mousemove touchmove', drag) - removeEventListeners(document, 'mouseup touchend', stop) - } - addEventListeners(document, 'mousemove touchmove', drag) - addEventListeners(document, 'mouseup touchend', stop) + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + }; + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); } - ) + ); } this.node.addEventListener('mousedown', event => { - setActivePanel(this.id) - this.moveToFront() - }) + setActivePanel(this.id); + this.moveToFront(); + }); this.handle.addEventListener('mousedown', event => { - setActivePanel(this.id) - this.moveToFront() - }) + setActivePanel(this.id); + this.moveToFront(); + }); // Add to slot if (!Blockbench.isMobile && !this.attached_to) { - let reference_panel = Panels[data.insert_before || data.insert_after] + let reference_panel = Panels[data.insert_before || data.insert_after]; this.moveTo( this.position_data.slot, reference_panel, reference_panel && !data.insert_after - ) + ); } - if (this.folded) this.fold(true) + if (this.folded) this.fold(true); - Panels[this.id] = this + Panels[this.id] = this; } isVisible() { return ( !this.folded && this.node.parentElement && this.node.parentElement.style.display !== 'none' - ) + ); } isInSidebar() { - return this.slot === 'left_bar' || this.slot === 'right_bar' + return this.slot === 'left_bar' || this.slot === 'right_bar'; } get position_data(): PanelPositionData { - return Interface.getModeData().panels[this.id] + return Interface.getModeData().panels[this.id]; } get slot() { - return this.position_data.slot + return this.position_data.slot; } get folded() { - return this.position_data.folded + return this.position_data.folded; } set folded(state) { - this.position_data.folded = !!state + this.position_data.folded = !!state; } get fixed_height() { - return this.position_data.fixed_height + return this.position_data.fixed_height; } set fixed_height(state) { - this.position_data.fixed_height = !!state + this.position_data.fixed_height = !!state; } get attached_to() { - let data = this.position_data.attached_to - return data + let data = this.position_data.attached_to; + return data; } set attached_to(id) { - this.position_data.attached_to = id + this.position_data.attached_to = id; } get attached_index() { - return this.position_data.attached_index + return this.position_data.attached_index; } set attached_index(id: number) { - this.position_data.attached_index = id + this.position_data.attached_index = id; } dispatchEvent(event_name: PanelEvent, data: any): void { - super.dispatchEvent(event_name, data) + super.dispatchEvent(event_name, data); } updatePositionData(data: Partial = {}) { - let position_data = this.position_data + let position_data = this.position_data; for (let key in DEFAULT_POSITION_DATA) { if (position_data[key] == undefined) { position_data[key] = this.default_position[key] ?? data[key] ?? - structuredClone(DEFAULT_POSITION_DATA[key]) + structuredClone(DEFAULT_POSITION_DATA[key]); } } } getAttachedPanels(): Panel[] { - let panels: Panel[] = [] + let panels: Panel[] = []; for (let id in Panels) { - let panel = Panels[id] as Panel + let panel = Panels[id] as Panel; if (panel.attached_to == this.id && Condition(panel) && panel != this) { - panels.push(panel) + panels.push(panel); } } - panels.sort((a, b) => b.attached_index - a.attached_index) - return panels + panels.sort((a, b) => b.attached_index - a.attached_index); + return panels; } /** * Get the host panel if this panel is attached to another panel */ getHostPanel(): Panel | undefined { - return Panels[this.attached_to] + return Panels[this.attached_to]; } /** * Get the panel that acts as the container for this panel. If the panel is not attached to another panel, returns itself */ getContainerPanel(): Panel { - return Panels[this.attached_to] || this + return Panels[this.attached_to] || this; } attachPanel(panel: Panel, index?: number) { - let old_host_panel = panel.getHostPanel() - panel.attached_to = this.id - if (index != undefined) panel.attached_index = index + let old_host_panel = panel.getHostPanel(); + panel.attached_to = this.id; + if (index != undefined) panel.attached_index = index; - this.update() + this.update(); if (old_host_panel) { - old_host_panel.update() + old_host_panel.update(); } - updateInterfacePanels() + updateInterfacePanels(); } selectTab(panel: Panel = this): this { if (this.open_attached_panel != panel) { - this.open_attached_panel = panel - this.update() + this.open_attached_panel = panel; + this.update(); } - return this + return this; } resetCustomLayout(): this { - if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {} + if (!Interface.getModeData().panels[this.id]) Interface.getModeData().panels[this.id] = {}; - this.updatePositionData() + this.updatePositionData(); for (let mode_id in Interface.data.modes) { - let mode_data = Interface.getModeData(mode_id) + let mode_data = Interface.getModeData(mode_id); if (!mode_data.panels[this.id]) - mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)) + mode_data.panels[this.id] = JSON.parse(JSON.stringify(this.position_data)); } - this.moveTo(this.slot) - this.fold(this.folded) - return this + this.moveTo(this.slot); + this.fold(this.folded); + return this; } addToolbar(toolbar: Toolbar, position = this.toolbars.length): void { - let nodes = [] + let nodes = []; if (toolbar.label) { let label = Interface.createElement( 'p', { class: 'panel_toolbar_label' }, tl(toolbar.name) - ) - nodes.push(label) - toolbar.label_node = label + ); + nodes.push(label); + toolbar.label_node = label; } - nodes.push(toolbar.node) + nodes.push(toolbar.node); if (position == 0) { - this.node.prepend(...nodes) + this.node.prepend(...nodes); } else if (typeof position == 'string') { - let anchor = this.node.querySelector(`.toolbar[toolbar_id="${position}"]`) + let anchor = this.node.querySelector(`.toolbar[toolbar_id="${position}"]`); if (anchor) { - anchor.after(...nodes) + anchor.after(...nodes); } } else { - this.node.append(...nodes) + this.node.append(...nodes); } - this.toolbars.splice(position, 0, toolbar) + this.toolbars.splice(position, 0, toolbar); } fold(state = !this.folded): this { - this.folded = !!state - let new_icon = Blockbench.getIconNode(state ? 'expand_less' : 'expand_more') - $(this.tab_bar).find('> .panel_folding_button > .icon').replaceWith(new_icon) - this.container.classList.toggle('folded', state) + this.folded = !!state; + let new_icon = Blockbench.getIconNode(state ? 'expand_less' : 'expand_more'); + $(this.tab_bar).find('> .panel_folding_button > .icon').replaceWith(new_icon); + this.container.classList.toggle('folded', state); if (this.onFold) { - this.onFold() + this.onFold(); } if (this.slot == 'top' || this.slot == 'bottom') { - resizeWindow() + resizeWindow(); } - this.update() - this.dispatchEvent('fold', {}) - return this + this.update(); + this.dispatchEvent('fold', {}); + return this; } setupFloatHandles(): this { let sides = [ @@ -831,314 +831,314 @@ export class Panel extends EventSystem { Interface.createElement('div', { class: 'panel_resize_side resize_bottom' }), Interface.createElement('div', { class: 'panel_resize_side resize_left' }), Interface.createElement('div', { class: 'panel_resize_side resize_right' }), - ] + ]; let corners = [ Interface.createElement('div', { class: 'panel_resize_corner resize_top_left' }), Interface.createElement('div', { class: 'panel_resize_corner resize_top_right' }), Interface.createElement('div', { class: 'panel_resize_corner resize_bottom_left' }), Interface.createElement('div', { class: 'panel_resize_corner resize_bottom_right' }), - ] + ]; let resize = (e1, direction_x, direction_y) => { - let position_before = this.position_data.float_position.slice() - let size_before = [this.width, this.height] - let started = false + let position_before = this.position_data.float_position.slice(); + let size_before = [this.width, this.height]; + let started = false; let drag = e2 => { - convertTouchEvent(e2) + convertTouchEvent(e2); if ( !started && Math.pow(e2.clientX - e1.clientX, 2) + Math.pow(e2.clientY - e1.clientY, 2) > 12 ) { - started = true + started = true; } - if (!started) return + if (!started) return; this.position_data.float_size[0] = - size_before[0] + (e2.clientX - e1.clientX) * direction_x + size_before[0] + (e2.clientX - e1.clientX) * direction_x; this.position_data.float_size[1] = - size_before[1] + (e2.clientY - e1.clientY) * direction_y + size_before[1] + (e2.clientY - e1.clientY) * direction_y; if (direction_x == -1) this.position_data.float_position[0] = - position_before[0] - this.position_data.float_size[0] + size_before[0] + position_before[0] - this.position_data.float_size[0] + size_before[0]; if (direction_y == -1) this.position_data.float_position[1] = - position_before[1] - this.position_data.float_size[1] + size_before[1] + position_before[1] - this.position_data.float_size[1] + size_before[1]; - this.update() - } + this.update(); + }; let stop = e2 => { - convertTouchEvent(e2) - - removeEventListeners(document, 'mousemove touchmove', drag) - removeEventListeners(document, 'mouseup touchend', stop) - } - addEventListeners(document, 'mousemove touchmove', drag) - addEventListeners(document, 'mouseup touchend', stop) - } - addEventListeners(sides[0], 'mousedown touchstart', event => resize(event, 0, -1)) - addEventListeners(sides[1], 'mousedown touchstart', event => resize(event, 0, 1)) - addEventListeners(sides[2], 'mousedown touchstart', event => resize(event, -1, 0)) - addEventListeners(sides[3], 'mousedown touchstart', event => resize(event, 1, 0)) - addEventListeners(corners[0], 'mousedown touchstart', event => resize(event, -1, -1)) - addEventListeners(corners[1], 'mousedown touchstart', event => resize(event, 1, -1)) - addEventListeners(corners[2], 'mousedown touchstart', event => resize(event, -1, 1)) - addEventListeners(corners[3], 'mousedown touchstart', event => resize(event, 1, 1)) + convertTouchEvent(e2); + + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + }; + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); + }; + addEventListeners(sides[0], 'mousedown touchstart', event => resize(event, 0, -1)); + addEventListeners(sides[1], 'mousedown touchstart', event => resize(event, 0, 1)); + addEventListeners(sides[2], 'mousedown touchstart', event => resize(event, -1, 0)); + addEventListeners(sides[3], 'mousedown touchstart', event => resize(event, 1, 0)); + addEventListeners(corners[0], 'mousedown touchstart', event => resize(event, -1, -1)); + addEventListeners(corners[1], 'mousedown touchstart', event => resize(event, 1, -1)); + addEventListeners(corners[2], 'mousedown touchstart', event => resize(event, -1, 1)); + addEventListeners(corners[3], 'mousedown touchstart', event => resize(event, 1, 1)); let handles = Interface.createElement('div', { class: 'panel_resize_handle_wrapper' }, [ ...sides, ...corners, - ]) - this.container.append(handles) - this.resize_handles = handles - return this + ]); + this.container.append(handles); + this.resize_handles = handles; + return this; } moveToFront(): this { if (this.slot == 'float' && Panel.floating_panel_z_order[0] !== this.id) { - Panel.floating_panel_z_order.remove(this.id) - Panel.floating_panel_z_order.splice(0, 0, this.id) - let zindex = 18 + Panel.floating_panel_z_order.remove(this.id); + Panel.floating_panel_z_order.splice(0, 0, this.id); + let zindex = 18; Panel.floating_panel_z_order.forEach(id => { - let panel = Panels[id] - panel.container.style.zIndex = zindex - panel.dispatchEvent('change_zindex', { zindex }) - zindex = Math.clamp(zindex - 1, 14, 19) - }) + let panel = Panels[id]; + panel.container.style.zIndex = zindex; + panel.dispatchEvent('change_zindex', { zindex }); + zindex = Math.clamp(zindex - 1, 14, 19); + }); } - return this + return this; } moveTo(slot: PanelSlot, ref_panel?: Panel, before = false): this { - let position_data = this.position_data + let position_data = this.position_data; if (slot == undefined) { - slot = ref_panel.position_data.slot + slot = ref_panel.position_data.slot; } if (slot !== this.slot) { - this.previous_slot = this.slot + this.previous_slot = this.slot; } // Reset attachment - this.position_data.attached_to = '' - this.position_data.attached_index = 0 - this.container.append(this.node) + this.position_data.attached_to = ''; + this.position_data.attached_index = 0; + this.container.append(this.node); this.dispatchEvent('move_to', { slot, ref_panel, before, previous_slot: this.previous_slot, - }) + }); - this.node.classList.remove('floating') + this.node.classList.remove('floating'); if (slot == 'left_bar' || slot == 'right_bar') { - let change_panel_order = !!ref_panel + let change_panel_order = !!ref_panel; if (!ref_panel && Interface.getModeData()[slot].includes(this.id)) { let panels = Interface.getModeData()[slot].filter( id => (Panels[id] && Panels[id].slot == slot) || id == this.id - ) - let index = panels.indexOf(this.id) + ); + let index = panels.indexOf(this.id); if (index == 0) { - ref_panel = Panels[panels[1]] - before = true + ref_panel = Panels[panels[1]]; + before = true; } else { - ref_panel = Panels[panels[index - 1]] - before = false + ref_panel = Panels[panels[index - 1]]; + before = false; } } if (ref_panel instanceof Panel && ref_panel.slot == slot) { if (before) { - $(ref_panel.node).before(this.node) + $(ref_panel.node).before(this.node); } else { - $(ref_panel.node).after(this.node) + $(ref_panel.node).after(this.node); } if (change_panel_order) { - Interface.getModeData()[slot].remove(this.id) + Interface.getModeData()[slot].remove(this.id); Interface.getModeData()[slot].splice( Interface.getModeData()[slot].indexOf(ref_panel.id) + (before ? 0 : 1), 0, this.id - ) + ); } } else { - document.getElementById(slot)!.append(this.container) - Interface.getModeData()[slot].safePush(this.id) + document.getElementById(slot)!.append(this.container); + Interface.getModeData()[slot].safePush(this.id); } } else if (slot == 'top') { - let top_panel = Interface.getTopPanel() + let top_panel = Interface.getTopPanel(); if ( top_panel && top_panel !== this && !Condition.mutuallyExclusive(this.condition, top_panel.condition) ) { - top_panel.moveTo(top_panel.previous_slot) + top_panel.moveTo(top_panel.previous_slot); } - document.getElementById('top_slot')!.append(this.container) + document.getElementById('top_slot')!.append(this.container); } else if (slot == 'bottom') { - let bottom_panel = Interface.getBottomPanel() + let bottom_panel = Interface.getBottomPanel(); if ( bottom_panel && bottom_panel !== this && !Condition.mutuallyExclusive(this.condition, bottom_panel.condition) ) { - bottom_panel.moveTo(bottom_panel.previous_slot) + bottom_panel.moveTo(bottom_panel.previous_slot); } - document.getElementById('bottom_slot')!.append(this.container) + document.getElementById('bottom_slot')!.append(this.container); } else if (slot == 'float' && !Blockbench.isMobile) { - Interface.work_screen.append(this.container) - this.node.classList.add('floating') - this.dispatchEvent('change_zindex', { zindex: 14 }) + Interface.work_screen.append(this.container); + this.node.classList.add('floating'); + this.dispatchEvent('change_zindex', { zindex: 14 }); if (!this.resize_handles) { - this.setupFloatHandles() + this.setupFloatHandles(); } } else if (slot == 'hidden' && !Blockbench.isMobile) { - this.node.remove() + this.node.remove(); } if (slot !== 'float') { - Panel.floating_panel_z_order.remove(this.id) - this.node.style.zIndex = '' - this.dispatchEvent('change_zindex', { zindex: null }) + Panel.floating_panel_z_order.remove(this.id); + this.node.style.zIndex = ''; + this.dispatchEvent('change_zindex', { zindex: null }); } - position_data.slot = slot + position_data.slot = slot; - this.updateSlot() + this.updateSlot(); if (Panels[this.id]) { this.dispatchEvent('moved_to', { slot, ref_panel, before, previous_slot: this.previous_slot, - }) + }); } - return this + return this; } updateSlot(): this { - let slot = this.slot + let slot = this.slot; - this.container.classList.remove('floating') + this.container.classList.remove('floating'); if (slot == 'left_bar' || slot == 'right_bar') { - document.getElementById(slot)!.append(this.container) - Interface.getModeData()[slot].safePush(this.id) + document.getElementById(slot)!.append(this.container); + Interface.getModeData()[slot].safePush(this.id); } else if (slot == 'top') { - document.getElementById('top_slot')!.append(this.container) + document.getElementById('top_slot')!.append(this.container); } else if (slot == 'bottom') { - document.getElementById('bottom_slot')!.append(this.container) + document.getElementById('bottom_slot')!.append(this.container); } else if (slot == 'float' && !Blockbench.isMobile) { - Interface.work_screen.append(this.container) - this.container.classList.add('floating') - this.dispatchEvent('change_zindex', { zindex: 14 }) + Interface.work_screen.append(this.container); + this.container.classList.add('floating'); + this.dispatchEvent('change_zindex', { zindex: 14 }); if (!this.resize_handles) { - this.setupFloatHandles() + this.setupFloatHandles(); } } else if (slot == 'hidden') { - this.container.remove() + this.container.remove(); } if (slot !== 'float') { - Panel.floating_panel_z_order.remove(this.id) - this.container.style.zIndex = '' - this.dispatchEvent('change_zindex', { zindex: null }) + Panel.floating_panel_z_order.remove(this.id); + this.container.style.zIndex = ''; + this.dispatchEvent('change_zindex', { zindex: null }); } if (this.folded != this.container.classList.contains('folded')) { - this.folded = !!this.folded - let new_icon = Blockbench.getIconNode(this.folded ? 'expand_less' : 'expand_more') - $(this.handle).find('> .panel_folding_button > .icon').replaceWith(new_icon) - this.container.classList.toggle('folded', this.folded) + this.folded = !!this.folded; + let new_icon = Blockbench.getIconNode(this.folded ? 'expand_less' : 'expand_more'); + $(this.handle).find('> .panel_folding_button > .icon').replaceWith(new_icon); + this.container.classList.toggle('folded', this.folded); if (this.onFold) { - this.onFold() + this.onFold(); } } - this.update() + this.update(); if (Panels[this.id]) { - TickUpdates.interface = true + TickUpdates.interface = true; } - return this + return this; } update(dragging: boolean = false) { - let show = BARS.condition(this.condition) + let show = BARS.condition(this.condition); if (!Blockbench.isMobile) { // Hide panel if its in host panel - if (this.getHostPanel() && Condition(this.getHostPanel().condition)) show = false + if (this.getHostPanel() && Condition(this.getHostPanel().condition)) show = false; } - let work_screen = document.querySelector('div#work_screen') - let center_screen = document.querySelector('div#center') - let slot = this.slot - let is_sidebar = slot == 'left_bar' || slot == 'right_bar' + let work_screen = document.querySelector('div#work_screen'); + let center_screen = document.querySelector('div#center'); + let slot = this.slot; + let is_sidebar = slot == 'left_bar' || slot == 'right_bar'; if (show) { - this.container.classList.remove('hidden') - this.node.classList.remove('attached') + this.container.classList.remove('hidden'); + this.node.classList.remove('attached'); if (slot == 'float') { if (!dragging && work_screen.clientWidth) { this.position_data.float_position[0] = Math.clamp( this.position_data.float_position[0], 0, work_screen.clientWidth - this.width - ) + ); this.position_data.float_position[1] = Math.clamp( this.position_data.float_position[1], 0, work_screen.clientHeight - this.height - ) + ); this.position_data.float_size[0] = Math.clamp( this.position_data.float_size[0], 200, work_screen.clientWidth - this.position_data.float_position[0] - ) + ); this.position_data.float_size[1] = Math.clamp( this.position_data.float_size[1], 86, work_screen.clientHeight - this.position_data.float_position[1] - ) + ); } - this.container.style.left = this.position_data.float_position[0] + 'px' - this.container.style.top = this.position_data.float_position[1] + 'px' - this.width = this.position_data.float_size[0] - this.height = this.position_data.float_size[1] - if (this.folded) this.height = this.tab_bar.clientHeight - this.container.style.width = this.width + 'px' - this.container.style.height = this.height + 'px' - this.container.classList.remove('bottommost_panel') - this.container.classList.remove('topmost_panel') + this.container.style.left = this.position_data.float_position[0] + 'px'; + this.container.style.top = this.position_data.float_position[1] + 'px'; + this.width = this.position_data.float_size[0]; + this.height = this.position_data.float_size[1]; + if (this.folded) this.height = this.tab_bar.clientHeight; + this.container.style.width = this.width + 'px'; + this.container.style.height = this.height + 'px'; + this.container.classList.remove('bottommost_panel'); + this.container.classList.remove('topmost_panel'); } else { this.container.style.width = this.container.style.left = this.container.style.top = - null + null; } if (Blockbench.isMobile) { - this.width = this.container.clientWidth + this.width = this.container.clientWidth; } else if (slot == 'left_bar') { - this.width = Interface.left_bar_width + this.width = Interface.left_bar_width; } else if (slot == 'right_bar') { - this.width = Interface.right_bar_width + this.width = Interface.right_bar_width; } if (slot == 'top' || slot == 'bottom') { if (Blockbench.isMobile && Blockbench.isLandscape) { - this.height = center_screen.clientHeight + this.height = center_screen.clientHeight; this.width = Math.clamp( this.position_data.height, 30, center_screen.clientWidth - ) - if (this.folded) this.width = 72 + ); + if (this.folded) this.width = 72; } else { let opposite_panel = - slot == 'top' ? Interface.getBottomPanel() : Interface.getTopPanel() + slot == 'top' ? Interface.getBottomPanel() : Interface.getTopPanel(); this.height = Math.clamp( this.position_data.height, 30, center_screen.clientHeight - (opposite_panel ? opposite_panel.height : 0) - ) - if (this.folded) this.height = this.tab_bar.clientHeight + ); + if (this.folded) this.height = this.tab_bar.clientHeight; this.width = Interface.work_screen.clientWidth - Interface.left_bar_width - - Interface.right_bar_width + Interface.right_bar_width; } - this.container.style.width = this.width + 'px' - this.container.style.height = this.height + 'px' + this.container.style.width = this.width + 'px'; + this.container.style.height = this.height + 'px'; } else if (is_sidebar) { if (this.fixed_height) { //let other_panels = slot == 'left_bar' ? Interface.getLeftPanels() : Interface.getRightPanels(); @@ -1148,17 +1148,17 @@ export class Panel extends EventSystem { this.position_data.height, 30, Interface.work_screen.clientHeight - ) - this.container.style.height = this.height + 'px' - this.container.classList.add('fixed_height') + ); + this.container.style.height = this.height + 'px'; + this.container.classList.add('fixed_height'); } else { - this.container.style.height = null + this.container.style.height = null; } } - if (!this.fixed_height) this.container.classList.remove('fixed_height') + if (!this.fixed_height) this.container.classList.remove('fixed_height'); if (this.sidebar_resize_handle) { - this.sidebar_resize_handle.style.display = is_sidebar ? 'block' : 'none' + this.sidebar_resize_handle.style.display = is_sidebar ? 'block' : 'none'; } if ( (slot == 'right_bar' && Interface.getRightPanels().last() == this) || @@ -1166,8 +1166,8 @@ export class Panel extends EventSystem { ) { this.node.parentElement?.childNodes.forEach((n: HTMLElement) => n.classList.remove('bottommost_panel') - ) - this.container.classList.add('bottommost_panel') + ); + this.container.classList.add('bottommost_panel'); } if ( (slot == 'right_bar' && Interface.getRightPanels()[0] == this) || @@ -1175,19 +1175,19 @@ export class Panel extends EventSystem { ) { this.node.parentElement?.childNodes.forEach((n: HTMLElement) => n.classList.remove('topmost_panel') - ) - this.container.classList.add('topmost_panel') + ); + this.container.classList.add('topmost_panel'); } if (this.node.clientHeight) { this.container.style.setProperty( '--main-panel-height', this.node.clientHeight + 'px' - ) + ); } - if (Panels[this.id] && this.onResize) this.onResize() + if (Panels[this.id] && this.onResize) this.onResize(); } else { - this.container.classList.add('hidden') + this.container.classList.add('hidden'); } if (!this.attached_to && !Blockbench.isMobile) { @@ -1196,49 +1196,49 @@ export class Panel extends EventSystem { this.open_attached_panel && this.getAttachedPanels().includes(this.open_attached_panel) == false ) { - this.open_attached_panel = this + this.open_attached_panel = this; } - let tabs: Panel[] = [this] - tabs.safePush(...this.getAttachedPanels()) - $(this.tab_bar.firstElementChild).empty() - let tab_amount = 0 + let tabs: Panel[] = [this]; + tabs.safePush(...this.getAttachedPanels()); + $(this.tab_bar.firstElementChild).empty(); + let tab_amount = 0; for (let panel of tabs) { - this.tab_bar.firstElementChild.append(panel.handle) - panel.handle.classList.toggle('selected', this.open_attached_panel == panel) - tab_amount++ + this.tab_bar.firstElementChild.append(panel.handle); + panel.handle.classList.toggle('selected', this.open_attached_panel == panel); + tab_amount++; } if (this.id == 'uv') { - this.id = 'uv' + this.id = 'uv'; } - let panel_is_appended = false + let panel_is_appended = false; for (let panel_node of this.container.querySelectorAll('.panel')) { if (panel_node == this.open_attached_panel.node) { - panel_is_appended = true + panel_is_appended = true; } else { - panel_node.remove() + panel_node.remove(); } } - if (!panel_is_appended) this.container.append(this.open_attached_panel.node) - this.open_attached_panel.node.classList.add('attached') - this.tab_bar.classList.toggle('single_tab', tab_amount <= 1) + if (!panel_is_appended) this.container.append(this.open_attached_panel.node); + this.open_attached_panel.node.classList.add('attached'); + this.tab_bar.classList.toggle('single_tab', tab_amount <= 1); } - this.dispatchEvent('update', { show }) - localStorage.setItem('interface_data', JSON.stringify(Interface.data)) - return this + this.dispatchEvent('update', { show }); + localStorage.setItem('interface_data', JSON.stringify(Interface.data)); + return this; } //Delete delete() { - delete Panels[this.id] - this.node.remove() - this.container.remove() - updateInterfacePanels() + delete Panels[this.id]; + this.node.remove(); + this.container.remove(); + updateInterfacePanels(); } - static floating_panel_z_order: string[] = [] + static floating_panel_z_order: string[] = []; } export interface Panel { - snap_menu: Menu + snap_menu: Menu; } Panel.prototype.snap_menu = new Menu([ { @@ -1252,8 +1252,8 @@ Panel.prototype.snap_menu = new Menu([ icon: 'align_horizontal_left', marked: panel => panel.slot == 'left_bar' && !panel.attached_to, click: panel => { - panel.fixed_height = false - panel.moveTo('left_bar') + panel.fixed_height = false; + panel.moveTo('left_bar'); }, }, { @@ -1261,8 +1261,8 @@ Panel.prototype.snap_menu = new Menu([ icon: 'align_horizontal_right', marked: panel => panel.slot == 'right_bar' && !panel.attached_to, click: panel => { - panel.fixed_height = false - panel.moveTo('right_bar') + panel.fixed_height = false; + panel.moveTo('right_bar'); }, }, { @@ -1270,8 +1270,8 @@ Panel.prototype.snap_menu = new Menu([ icon: 'align_vertical_top', marked: panel => panel.slot == 'top' && !panel.attached_to, click: panel => { - panel.fixed_height = false - panel.moveTo('top') + panel.fixed_height = false; + panel.moveTo('top'); }, }, { @@ -1279,8 +1279,8 @@ Panel.prototype.snap_menu = new Menu([ icon: 'align_vertical_bottom', marked: panel => panel.slot == 'bottom' && !panel.attached_to, click: panel => { - panel.fixed_height = false - panel.moveTo('bottom') + panel.fixed_height = false; + panel.moveTo('bottom'); }, }, { @@ -1288,8 +1288,8 @@ Panel.prototype.snap_menu = new Menu([ icon: 'web_asset', marked: panel => panel.slot == 'float' && !panel.attached_to, click: panel => { - panel.fixed_height = false - panel.moveTo('float') + panel.fixed_height = false; + panel.moveTo('float'); }, }, '_', @@ -1299,8 +1299,8 @@ Panel.prototype.snap_menu = new Menu([ marked: panel => panel.slot == 'hidden' && !panel.attached_to, condition: panel => panel.optional && panel.slot != 'hidden', click: panel => { - panel.fixed_height = false - panel.moveTo('hidden') + panel.fixed_height = false; + panel.moveTo('hidden'); }, }, ], @@ -1311,26 +1311,26 @@ Panel.prototype.snap_menu = new Menu([ icon: 'fa-diagram-next', condition: () => !Blockbench.isMobile, children: (panel: Panel) => { - let options: CustomMenuItem[] = [] + let options: CustomMenuItem[] = []; for (let id in Panels) { - let panel2: Panel = Panels[id] + let panel2: Panel = Panels[id]; if ( !Condition(panel2.condition) || panel2.attached_to || panel2.id == panel.attached_to || panel2 == panel ) - continue + continue; options.push({ id: panel2.id, name: panel2.name, icon: panel2.icon, click() { - panel2.attachPanel(panel) + panel2.attachPanel(panel); }, - }) + }); } - return options + return options; }, }, { @@ -1340,7 +1340,7 @@ Panel.prototype.snap_menu = new Menu([ condition: (panel: Panel) => panel.getContainerPanel().slot != 'hidden' && !Blockbench.isMobile, click(panel: Panel) { - panel.getContainerPanel().fold() + panel.getContainerPanel().fold(); }, }, { @@ -1349,115 +1349,115 @@ Panel.prototype.snap_menu = new Menu([ icon: (panel: Panel) => panel.slot != 'hidden', condition: (panel: Panel) => Blockbench.isMobile, click(panel: Panel) { - panel.fixed_height = false + panel.fixed_height = false; if (panel.slot == 'hidden') { - panel.moveTo('bottom') + panel.moveTo('bottom'); } else { - panel.moveTo('hidden') + panel.moveTo('hidden'); } }, }, -]) +]); export function setupPanels() { Interface.panel_definers.forEach(definer => { if (typeof definer === 'function') { - definer() + definer(); } - }) - updateSidebarOrder() + }); + updateSidebarOrder(); } export function updateInterfacePanels() { if (!Blockbench.isMobile) { - Interface.left_bar.style.display = Prop.show_left_bar ? 'flex' : 'none' - Interface.right_bar.style.display = Prop.show_right_bar ? 'flex' : 'none' + Interface.left_bar.style.display = Prop.show_left_bar ? 'flex' : 'none'; + Interface.right_bar.style.display = Prop.show_right_bar ? 'flex' : 'none'; } Interface.work_screen.style.setProperty( 'grid-template-columns', Interface.left_bar_width + 'px auto ' + Interface.right_bar_width + 'px' - ) + ); for (var key in Interface.Panels) { - var panel: Panel = Panels[key] - panel.update() + var panel: Panel = Panels[key]; + panel.update(); } var left_width = Interface.left_bar.querySelector('.panel_container:not(.hidden)') ? Interface.left_bar_width - : 0 + : 0; var right_width = Interface.right_bar.querySelector('.panel_container:not(.hidden)') ? Interface.right_bar_width - : 0 + : 0; if (!left_width || !right_width) { Interface.work_screen.style.setProperty( 'grid-template-columns', left_width + 'px auto ' + right_width + 'px' - ) + ); } - Interface.preview.style.visibility = Interface.preview.clientHeight > 80 ? 'visible' : 'hidden' + Interface.preview.style.visibility = Interface.preview.clientHeight > 80 ? 'visible' : 'hidden'; - let height = document.getElementById('center')!.clientHeight - height -= Interface.getBottomPanel()?.height || 0 - height -= Interface.getTopPanel()?.height || 0 - Interface.preview.style.height = height > 0 ? height + 'px' : '' + let height = document.getElementById('center')!.clientHeight; + height -= Interface.getBottomPanel()?.height || 0; + height -= Interface.getTopPanel()?.height || 0; + Interface.preview.style.height = height > 0 ? height + 'px' : ''; if (Preview.split_screen.enabled) { - Preview.split_screen.updateSize() + Preview.split_screen.updateSize(); } for (var key in Interface.Resizers) { - var resizer = Interface.Resizers[key] - resizer.update() + var resizer = Interface.Resizers[key]; + resizer.update(); } - updateSidebarOrder() + updateSidebarOrder(); } export function updateSidebarOrder() { - ;['left_bar', 'right_bar'].forEach(bar => { - let bar_node = document.querySelector(`.sidebar#${bar}`) + ['left_bar', 'right_bar'].forEach(bar => { + let bar_node = document.querySelector(`.sidebar#${bar}`); let current_panels = Array.from(bar_node.childNodes).map(panel_node => (panel_node as HTMLElement).getAttribute('panel_id') - ) + ); - let last_panel: Panel - let panel_count = 0 + let last_panel: Panel; + let panel_count = 0; Interface.getModeData()[bar].forEach((panel_id: string) => { - let panel: Panel = Panels[panel_id] + let panel: Panel = Panels[panel_id]; if (panel && panel.slot == bar) { - panel.container.classList.remove('bottommost_panel') - panel.container.classList.remove('topmost_panel') + panel.container.classList.remove('bottommost_panel'); + panel.container.classList.remove('topmost_panel'); if (!panel.attached_to && Condition(panel.condition)) { if (current_panels[panel_count] != panel_id) { - bar_node.append(panel.container) + bar_node.append(panel.container); } if (panel_count == 0) { - panel.container.classList.add('topmost_panel') + panel.container.classList.add('topmost_panel'); } - panel_count++ - last_panel = panel + panel_count++; + last_panel = panel; } else { - panel.container.remove() + panel.container.remove(); } } - }) + }); if (last_panel && panel_count > 1) { - last_panel.container.classList.add('bottommost_panel') + last_panel.container.classList.add('bottommost_panel'); } - }) + }); } export function updatePanelSelector() { - if (!Blockbench.isMobile) return + if (!Blockbench.isMobile) return; - Interface.PanelSelectorVue.$forceUpdate() - let bottom_panel = Interface.getBottomPanel() + Interface.PanelSelectorVue.$forceUpdate(); + let bottom_panel = Interface.getBottomPanel(); if (bottom_panel && !Condition(bottom_panel.display_condition)) { - Interface.PanelSelectorVue.select(null) + Interface.PanelSelectorVue.select(null); } } export function setActivePanel(panel) { - Prop.active_panel = panel + Prop.active_panel = panel; } export function setupMobilePanelSelector() { @@ -1471,36 +1471,36 @@ export function setupMobilePanelSelector() { computed: {}, methods: { panels() { - let arr = [] + let arr = []; for (var id in this.all_panels) { - let panel = this.all_panels[id] + let panel = this.all_panels[id]; if ( Condition(panel.condition) && Condition(panel.display_condition) && panel.slot != 'hidden' ) { - arr.push(panel) + arr.push(panel); } } - return arr + return arr; }, select(panel: Panel) { - this.selected = panel && panel.id + this.selected = panel && panel.id; for (let key in Panels) { - let panel_b = Panels[key] + let panel_b = Panels[key]; if (panel_b.slot == 'bottom') { - $(panel_b.container).detach() - panel_b.position_data.slot = 'left_bar' + $(panel_b.container).detach(); + panel_b.position_data.slot = 'left_bar'; } } if (panel) { - panel.moveTo('bottom') + panel.moveTo('bottom'); } else { - resizeWindow() + resizeWindow(); } }, openKeyboardMenu() { - openTouchKeyboardModifierMenu(this.$refs.mobile_keyboard_menu) + openTouchKeyboardModifierMenu(this.$refs.mobile_keyboard_menu); }, Condition, getIconNode: Blockbench.getIconNode, @@ -1517,7 +1517,7 @@ export function setupMobilePanelSelector() { keyboard `, - }) + }); } Object.assign(window, { @@ -1528,4 +1528,4 @@ Object.assign(window, { updatePanelSelector, setActivePanel, setupMobilePanelSelector, -}) +}); diff --git a/js/interface/settings.ts b/js/interface/settings.ts index e9d81b3da..d6a671432 100644 --- a/js/interface/settings.ts +++ b/js/interface/settings.ts @@ -1,13 +1,13 @@ -import { Vue } from '../lib/libs' -import { Blockbench } from '../api' -import { Dialog } from './dialog' -import { FormInputType } from './form' -import { ipcRenderer } from '../native_apis' +import { Vue } from '../lib/libs'; +import { Blockbench } from '../api'; +import { Dialog } from './dialog'; +import { FormInputType } from './form'; +import { ipcRenderer } from '../native_apis'; -export const settings: Record = {} -export type settings_type = typeof settings +export const settings: Record = {}; +export type settings_type = typeof settings; -type SettingsValue = string | number | boolean +type SettingsValue = string | number | boolean; enum SettingsType { Toggle = 'toggle', Number = 'number', @@ -17,144 +17,144 @@ enum SettingsType { Click = 'click', } interface SettingOptions { - name?: string - type?: SettingsType | `${SettingsType}` - value?: boolean | number | string - condition?: ConditionResolvable - category?: string - description?: string - requires_restart?: boolean - launch_setting?: boolean - min?: number - max?: number - step?: number - icon?: string - plugin?: string - click?(): void + name?: string; + type?: SettingsType | `${SettingsType}`; + value?: boolean | number | string; + condition?: ConditionResolvable; + category?: string; + description?: string; + requires_restart?: boolean; + launch_setting?: boolean; + min?: number; + max?: number; + step?: number; + icon?: string; + plugin?: string; + click?(): void; options?: { - [id: string]: string - } - onChange?(value: any): void + [id: string]: string; + }; + onChange?(value: any): void; } /** * Settings can be used to add global configuration options to Blockbench. All settings are listed under File > Preferences > Settings. */ export class Setting { - id: string - type: SettingsType - default_value: SettingsValue + id: string; + type: SettingsType; + default_value: SettingsValue; /** * The master value, not affected by profiles */ - master_value: SettingsValue - condition: ConditionResolvable - category: string - name: string - description: string - requires_restart: boolean - launch_setting: boolean - plugin?: string - min?: number - max?: number - step?: number - icon: string - click: (event: MouseEvent | KeyboardEvent) => void - options: Record - hidden: boolean - onChange: (value: SettingsValue) => void - keybind_label: string + master_value: SettingsValue; + condition: ConditionResolvable; + category: string; + name: string; + description: string; + requires_restart: boolean; + launch_setting: boolean; + plugin?: string; + min?: number; + max?: number; + step?: number; + icon: string; + click: (event: MouseEvent | KeyboardEvent) => void; + options: Record; + hidden: boolean; + onChange: (value: SettingsValue) => void; + keybind_label: string; constructor(id: string, data: SettingOptions) { - this.id = id - settings[id] = this - this.type = SettingsType.Toggle - if (data.type) this.type = data.type as SettingsType + this.id = id; + settings[id] = this; + this.type = SettingsType.Toggle; + if (data.type) this.type = data.type as SettingsType; if (data.value != undefined) { - this.default_value = data.value + this.default_value = data.value; } else { switch (this.type) { case 'toggle': - this.default_value = true - break + this.default_value = true; + break; case 'number': - this.default_value = 0 - break + this.default_value = 0; + break; case 'text': - this.default_value = '' - break + this.default_value = ''; + break; case 'password': - this.default_value = '' - break + this.default_value = ''; + break; case 'select': - this.default_value - break + this.default_value; + break; case 'click': - this.default_value = false - break + this.default_value = false; + break; } } if (typeof Settings.stored[id] === 'object') { // @ts-ignore - this.master_value = Settings.stored[id].value + this.master_value = Settings.stored[id].value; } else if (data.value != undefined) { - this.master_value = data.value + this.master_value = data.value; } else { - this.master_value = this.default_value - } - this.condition = data.condition - this.category = data.category || 'general' - this.name = data.name || tl(`settings.${id}`) - this.description = data.description || tl(`settings.${id}.desc`) - this.requires_restart = data.requires_restart == true - this.launch_setting = data.launch_setting || false + this.master_value = this.default_value; + } + this.condition = data.condition; + this.category = data.category || 'general'; + this.name = data.name || tl(`settings.${id}`); + this.description = data.description || tl(`settings.${id}.desc`); + this.requires_restart = data.requires_restart == true; + this.launch_setting = data.launch_setting || false; // @ts-ignore plugin code is loaded after this, so "Plugins" cannot be imported here this.plugin = - data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : '') + data.plugin || (typeof Plugins != 'undefined' ? Plugins.currently_loading : ''); if (this.type == 'number') { - this.min = data.min - this.max = data.max - this.step = data.step + this.min = data.min; + this.max = data.max; + this.step = data.step; } if (this.type == 'click') { - this.icon = data.icon - this.click = data.click + this.icon = data.icon; + this.click = data.click; } if (this.type == 'select') { - this.options = data.options + this.options = data.options; } if (this.type == 'password') { - this.hidden = true + this.hidden = true; } if (typeof data.onChange == 'function') { - this.onChange = data.onChange + this.onChange = data.onChange; } //add to structure - var category = Settings.structure[this.category] + var category = Settings.structure[this.category]; if (category) { - category.items[id] = this - let before = category.open - category.open = false + category.items[id] = this; + let before = category.open; + category.open = false; Vue.nextTick(() => { - category.open = before - }) + category.open = before; + }); } if (!this.icon) { if (this.type == 'toggle') - this.icon = this.value ? 'check_box' : 'check_box_outline_blank' - if (this.type == 'number') this.icon = 'tag' - if (this.type == 'password') this.icon = 'password' - if (this.type == 'text') this.icon = 'format_color_text' - if (this.type == 'select') this.icon = 'list' - if (!this.icon) this.icon = 'settings' - } - this.keybind_label = tl('data.setting') + this.icon = this.value ? 'check_box' : 'check_box_outline_blank'; + if (this.type == 'number') this.icon = 'tag'; + if (this.type == 'password') this.icon = 'password'; + if (this.type == 'text') this.icon = 'format_color_text'; + if (this.type == 'select') this.icon = 'list'; + if (!this.icon) this.icon = 'settings'; + } + this.keybind_label = tl('data.setting'); if (Blockbench.setup_successful) { - Settings.saveLocalStorages() + Settings.saveLocalStorages(); } } /** @@ -163,99 +163,99 @@ export class Setting { get value(): SettingsValue { let profile = SettingsProfile.all.find( profile => profile.isActive() && profile.settings[this.id] !== undefined - ) + ); if (profile) { - return profile.settings[this.id] ?? this.master_value + return profile.settings[this.id] ?? this.master_value; } else { - return this.master_value + return this.master_value; } } set value(value: SettingsValue) { - this.master_value = value + this.master_value = value; } /** * The value that is displayed in the settings dialog */ get ui_value(): SettingsValue { - let profile = Settings.dialog.content_vue?.$data.profile + let profile = Settings.dialog.content_vue?.$data.profile; if (profile) { - return profile.settings[this.id] ?? this.master_value + return profile.settings[this.id] ?? this.master_value; } else { - return this.master_value + return this.master_value; } } set ui_value(value: SettingsValue) { - let profile = Settings.dialog.content_vue?.$data.profile - if (this.type == 'number') value = Math.clamp(value as number, this.min, this.max) + let profile = Settings.dialog.content_vue?.$data.profile; + if (this.type == 'number') value = Math.clamp(value as number, this.min, this.max); if (profile) { - Vue.set(profile.settings, this.id, value) + Vue.set(profile.settings, this.id, value); } else { - this.master_value = value + this.master_value = value; } } delete() { if (settings[this.id]) { - delete settings[this.id] + delete settings[this.id]; } if (Settings.structure[this.category] && Settings.structure[this.category].items[this.id]) { - delete Settings.structure[this.category].items[this.id] + delete Settings.structure[this.category].items[this.id]; } } /** * Sets the value of the setting, while triggering the onChange function if available, and saving the change. */ set(value: SettingsValue) { - if (value === undefined || value === null) return - let old_value = this.value + if (value === undefined || value === null) return; + let old_value = this.value; if (this.type == 'number' && typeof value == 'number') { if (this.step) { - value = Math.round(value / this.step) * this.step + value = Math.round(value / this.step) * this.step; } - this.value = Math.clamp(value, this.min, this.max) + this.value = Math.clamp(value, this.min, this.max); } else if (this.type == 'toggle') { - this.value = !!value + this.value = !!value; } else if (this.type == 'click') { - this.value = value + this.value = value; } else if (typeof value == 'string') { - this.value = value + this.value = value; } if (typeof this.onChange == 'function' && this.value !== old_value) { - this.onChange(this.value) + this.onChange(this.value); } - Settings.saveLocalStorages() + Settings.saveLocalStorages(); } /** * Triggers the setting, as if selected in action control. This toggles boolean settings, opens a dialog for string or numeric settings, etc. */ trigger(e: KeyboardEvent | MouseEvent) { - let { type } = this - let setting = this + let { type } = this; + let setting = this; if (type == 'toggle') { - this.set(!this.value) - Settings.save() + this.set(!this.value); + Settings.save(); if (setting.requires_restart) { - Settings.showRestartMessage() + Settings.showRestartMessage(); } } else if (type == 'click') { - this.click(e) + this.click(e); } else if (type == 'select') { - let list = [] + let list = []; for (let key in this.options) { list.push({ id: key, name: this.options[key], icon: this.value == key ? 'far.fa-dot-circle' : 'far.fa-circle', click: () => { - this.set(key) - Settings.save() + this.set(key); + Settings.save(); if (setting.requires_restart) { - Settings.showRestartMessage() + Settings.showRestartMessage(); } }, - }) + }); } - new Menu(list).open(e.target as HTMLElement) + new Menu(list).open(e.target as HTMLElement); } else { let input_types: Record = { click: 'checkbox', @@ -264,7 +264,7 @@ export class Setting { select: 'select', text: 'text', toggle: 'checkbox', - } + }; let dialog = new Dialog({ id: 'setting_' + this.id, title: tl('data.setting'), @@ -285,22 +285,22 @@ export class Setting { type: 'buttons', buttons: ['dialog.settings.reset_to_default'], click() { - dialog.setFormValues({ input: setting.default_value }) + dialog.setFormValues({ input: setting.default_value }); }, }, }, onConfirm({ input }) { - setting.set(input) - Settings.save() - this.hide().delete() + setting.set(input); + Settings.save(); + this.hide().delete(); if (setting.requires_restart) { - Settings.showRestartMessage() + Settings.showRestartMessage(); } }, onCancel() { - this.hide().delete() + this.hide().delete(); }, - }).show() + }).show(); } } } @@ -311,98 +311,98 @@ enum SettingsProfileConditionType { file_path = 'file_path', } interface SettingsProfileData { - name?: string - color?: number + name?: string; + color?: number; } export class SettingsProfile { - uuid: string - name: string - color: number + uuid: string; + name: string; + color: number; condition: { - type: SettingsProfileConditionType - value: string - } - settings: Record - selected: boolean + type: SettingsProfileConditionType; + value: string; + }; + settings: Record; + selected: boolean; constructor(data: SettingsProfileData = {}) { - this.uuid = guid() - this.name = data.name || 'New Profile' + this.uuid = guid(); + this.name = data.name || 'New Profile'; this.color = - data.color == undefined ? Math.randomInteger(0, markerColors.length - 1) : data.color + data.color == undefined ? Math.randomInteger(0, markerColors.length - 1) : data.color; this.condition = { type: SettingsProfileConditionType.selectable, value: '', - } - this.settings = {} - this.extend(data) - this.selected = false - SettingsProfile.all.push(this) + }; + this.settings = {}; + this.extend(data); + this.selected = false; + SettingsProfile.all.push(this); } select(update = true) { - if (this.condition.type !== SettingsProfileConditionType.selectable) return + if (this.condition.type !== SettingsProfileConditionType.selectable) return; - SettingsProfile.all.forEach(p => (p.selected = false)) - this.selected = true - SettingsProfile.selected = this + SettingsProfile.all.forEach(p => (p.selected = false)); + this.selected = true; + SettingsProfile.selected = this; if (update) { - Settings.updateSettingsInProfiles() - Settings.saveLocalStorages() - Settings.updateProfileButton() + Settings.updateSettingsInProfiles(); + Settings.saveLocalStorages(); + Settings.updateProfileButton(); } } extend(data) { - Merge.string(this, data, 'name') + Merge.string(this, data, 'name'); if (data.condition) { - this.condition.type = data.condition.type - this.condition.value = data.condition.value + this.condition.type = data.condition.type; + this.condition.value = data.condition.value; } if (data.settings) { for (let key in data.settings) { - let value = data.settings[key] - if (value === undefined || value === null) continue - Vue.set(this.settings, key, value) + let value = data.settings[key]; + if (value === undefined || value === null) continue; + Vue.set(this.settings, key, value); } } } isActive() { switch (this.condition.type) { case SettingsProfileConditionType.selectable: - return SettingsProfile.selected == this + return SettingsProfile.selected == this; case SettingsProfileConditionType.format: - if (Format && Format.id == this.condition.value) return true - break + if (Format && Format.id == this.condition.value) return true; + break; case SettingsProfileConditionType.file_path: - let regex = new RegExp(this.condition.value, 'i') + let regex = new RegExp(this.condition.value, 'i'); if ( Project && (regex.test(Project.save_path.replace(osfs, '/')) || regex.test(Project.export_path.replace(osfs, '/'))) ) { - return true + return true; } - break + break; } - return false + return false; } clear(key) { - Vue.delete(this.settings, key) - Settings.saveLocalStorages() + Vue.delete(this.settings, key); + Settings.saveLocalStorages(); } openDialog() { - let color_options = {} + let color_options = {}; for (let i = 0; i < markerColors.length; i++) { - color_options[i] = tl(`cube.color.${markerColors[i].id}`) + color_options[i] = tl(`cube.color.${markerColors[i].id}`); } let condition_types = { selectable: tl('settings_profile.condition.type.selectable'), format: tl('data.format'), file_path: tl('data.file_path'), - } - let formats = {} + }; + let formats = {}; for (let key in Formats) { - formats[key] = Formats[key].name + formats[key] = Formats[key].name; } let dialog = new Dialog({ id: 'settings_profile', @@ -443,42 +443,42 @@ export class SettingsProfile { type: 'buttons', buttons: ['generic.delete'], click: button => { - if (confirm(tl('settings_profile.confirm_delete'))) this.remove() - Settings.dialog.content_vue.$data.profile = null - SettingsProfile.unselect() - dialog.close(0) + if (confirm(tl('settings_profile.confirm_delete'))) this.remove(); + Settings.dialog.content_vue.$data.profile = null; + SettingsProfile.unselect(); + dialog.close(0); }, }, }, onConfirm: result => { - this.name = result.name - this.color = result.color - this.condition.type = result.condition_type - if (this.condition.type == 'format') this.condition.value = result.format - if (this.condition.type == 'file_path') this.condition.value = result.file_path - Settings.saveLocalStorages() - Settings.updateProfileButton() + this.name = result.name; + this.color = result.color; + this.condition.type = result.condition_type; + if (this.condition.type == 'format') this.condition.value = result.format; + if (this.condition.type == 'file_path') this.condition.value = result.file_path; + Settings.saveLocalStorages(); + Settings.updateProfileButton(); }, onCancel() { - Settings.updateProfileButton() + Settings.updateProfileButton(); }, - }).show() + }).show(); } remove() { - SettingsProfile.all.remove(this) - Settings.saveLocalStorages() + SettingsProfile.all.remove(this); + Settings.saveLocalStorages(); } - static all: SettingsProfile[] = [] - static selected: SettingsProfile | null = null + static all: SettingsProfile[] = []; + static selected: SettingsProfile | null = null; static unselect = function (update = true) { - SettingsProfile.all.forEach(p => (p.selected = false)) - SettingsProfile.selected = null + SettingsProfile.all.forEach(p => (p.selected = false)); + SettingsProfile.selected = null; if (update) { - Settings.updateSettingsInProfiles() - Settings.saveLocalStorages() - Settings.updateProfileButton() + Settings.updateSettingsInProfiles(); + Settings.saveLocalStorages(); + Settings.updateProfileButton(); } - } + }; } /** @@ -497,77 +497,77 @@ export const Settings = { name: data.name || tl('settings.category.' + id), open: data.open != undefined ? !!data.open : id === 'general', items: {}, - } - Settings.dialog.sidebar.pages[id] = Settings.structure[id].name - Settings.dialog.sidebar.build() + }; + Settings.dialog.sidebar.pages[id] = Settings.structure[id].name; + Settings.dialog.sidebar.build(); }, /** * Save all settings to the local storage */ saveLocalStorages() { - var settings_copy = {} + var settings_copy = {}; for (var key in settings) { - settings_copy[key] = { value: settings[key].master_value } + settings_copy[key] = { value: settings[key].master_value }; } - localStorage.setItem('settings', JSON.stringify(settings_copy)) - localStorage.setItem('settings_profiles', JSON.stringify(SettingsProfile.all)) + localStorage.setItem('settings', JSON.stringify(settings_copy)); + localStorage.setItem('settings_profiles', JSON.stringify(SettingsProfile.all)); // @ts-ignore - if (window.ColorPanel) ColorPanel.saveLocalStorages() + if (window.ColorPanel) ColorPanel.saveLocalStorages(); }, /** * Save the settings and apply changes */ save() { - Settings.saveLocalStorages() - updateSelection() + Settings.saveLocalStorages(); + updateSelection(); for (let key in BarItems) { - let action = BarItems[key] + let action = BarItems[key]; if (action instanceof Toggle && action.linked_setting) { if ( settings[action.linked_setting] && action.value != settings[action.linked_setting].value ) { - action.value = settings[action.linked_setting].value as boolean - action.updateEnabledState() + action.value = settings[action.linked_setting].value as boolean; + action.updateEnabledState(); } } } - Settings.updateProfileButton() - Blockbench.dispatchEvent('update_settings', {}) + Settings.updateProfileButton(); + Blockbench.dispatchEvent('update_settings', {}); }, updateSettingsInProfiles() { - let settings_to_change = new Set() + let settings_to_change = new Set(); for (let profile of SettingsProfile.all) { for (let key in profile.settings) { if (settings[key]) { - settings_to_change.add(key) + settings_to_change.add(key); } else { - delete profile.settings[key] + delete profile.settings[key]; } } } settings_to_change.forEach((key: string) => { - let setting = settings[key] - if (setting.onChange) setting.onChange(setting.value) - }) + let setting = settings[key]; + if (setting.onChange) setting.onChange(setting.value); + }); }, updateProfileButton() { - let profile = SettingsProfile.selected + let profile = SettingsProfile.selected; Settings.profile_menu_button.style.color = profile ? markerColors[profile.color % markerColors.length].standard - : '' + : ''; Settings.profile_menu_button.classList.toggle( 'hidden', SettingsProfile.all.findIndex(p => p.condition.type == 'selectable') == -1 - ) + ); }, import(file) { - let data = JSON.parse(file.content) + let data = JSON.parse(file.content); for (let key in settings) { - let setting = settings[key] + let setting = settings[key]; if (setting instanceof Setting && data.settings[key] !== undefined) { - setting.set(data.settings[key]) + setting.set(data.settings[key]); } } }, @@ -576,26 +576,27 @@ export const Settings = { */ get(id: string) { if (id && settings[id]) { - return settings[id].value + return settings[id].value; } }, openDialog(options: { search_term?: string; profile?: string } = {}) { for (var sett in settings) { if (settings.hasOwnProperty(sett)) { - Settings.old[sett] = settings[sett].value + Settings.old[sett] = settings[sett].value; } } - Settings.dialog.show() - if (options.search_term) Settings.dialog.content_vue.$data.search_term = options.search_term - if (options.profile) Settings.dialog.content_vue.$data.profile = options.profile - Settings.dialog.content_vue.$forceUpdate() + Settings.dialog.show(); + if (options.search_term) + Settings.dialog.content_vue.$data.search_term = options.search_term; + if (options.profile) Settings.dialog.content_vue.$data.profile = options.profile; + Settings.dialog.content_vue.$forceUpdate(); }, showRestartMessage(settings?: Setting[]) { - let message + let message; if (settings instanceof Array) { - message = tl('message.settings_require_restart.message') + '\n\n' + message = tl('message.settings_require_restart.message') + '\n\n'; for (let setting of settings) { - message += '* ' + setting.name + '\n' + message += '* ' + setting.name + '\n'; } } Blockbench.showMessageBox( @@ -612,22 +613,22 @@ export const Settings = { if (result == 'restart_now') { if (isApp) { Blockbench.once('before_closing', () => { - ipcRenderer.send('new-window') - }) - window.close() + ipcRenderer.send('new-window'); + }); + window.close(); } else { - location.reload() + location.reload(); } } } - ) + ); }, old: {}, -} +}; Object.assign(window, { settings, Setting, SettingsProfile, Settings, -}) +}); diff --git a/js/interface/settings_window.ts b/js/interface/settings_window.ts index f7652f7c2..940d0780e 100644 --- a/js/interface/settings_window.ts +++ b/js/interface/settings_window.ts @@ -1,9 +1,9 @@ -import { Blockbench } from '../api' -import { ipcRenderer } from '../native_apis' -import { Plugins } from '../plugin_loader' -import { compileJSON } from '../util/json' -import { Dialog } from './dialog' -import { Setting, SettingsProfile } from './settings' +import { Blockbench } from '../api'; +import { ipcRenderer } from '../native_apis'; +import { Plugins } from '../plugin_loader'; +import { compileJSON } from '../util/json'; +import { Dialog } from './dialog'; +import { Setting, SettingsProfile } from './settings'; BARS.defineActions(() => { new Action('settings_window', { @@ -12,13 +12,13 @@ BARS.defineActions(() => { click: function () { for (var sett in settings) { if (settings.hasOwnProperty(sett)) { - Settings.old[sett] = settings[sett].value + Settings.old[sett] = settings[sett].value; } } - Settings.dialog.show() - ;(document.querySelector('dialog#settings .search_bar > input') as HTMLElement).focus() + Settings.dialog.show(); + (document.querySelector('dialog#settings .search_bar > input') as HTMLElement).focus(); }, - }) + }); new Action('import_settings', { icon: 'folder', @@ -32,21 +32,21 @@ BARS.defineActions(() => { type: 'Blockbench Settings', }, function (files) { - Settings.import(files[0]) + Settings.import(files[0]); } - ) + ); }, - }) + }); new Action('export_settings', { icon: 'fas.fa-user-cog', category: 'blockbench', click: async function () { - let private_data = [] - var settings_copy = {} + let private_data = []; + var settings_copy = {}; for (var key in settings) { - settings_copy[key] = settings[key].value + settings_copy[key] = settings[key].value; if (settings[key].value && settings[key].type == 'password') { - private_data.push(key) + private_data.push(key); } } if (private_data.length) { @@ -66,14 +66,14 @@ BARS.defineActions(() => { result => { if (result == 1) { private_data.forEach(key => { - delete settings_copy[key] - }) + delete settings_copy[key]; + }); } - resolve(result !== 2) + resolve(result !== 2); } - ) - }) - if (!go_on) return + ); + }); + if (!go_on) return; } // @ts-ignore for now Blockbench.export({ @@ -81,41 +81,41 @@ BARS.defineActions(() => { type: 'Blockbench Settings', extensions: ['bbsettings'], content: compileJSON({ settings: settings_copy }), - }) + }); }, - }) - let title_bar = document.getElementById('settings_title_bar') - BarItems.import_settings.toElement(title_bar) - BarItems.export_settings.toElement(title_bar) -}) + }); + let title_bar = document.getElementById('settings_title_bar'); + BarItems.import_settings.toElement(title_bar); + BarItems.export_settings.toElement(title_bar); +}); onVueSetup(function () { for (var key in settings) { - if (settings[key].condition == false) continue - var category = settings[key].category - if (!category) category = 'general' + if (settings[key].condition == false) continue; + var category = settings[key].category; + if (!category) category = 'general'; if (!Settings.structure[category]) { Settings.structure[category] = { name: tl('settings.category.' + category), open: category === 'general', items: {}, - } + }; } - Settings.structure[category].items[key] = settings[key] + Settings.structure[category].items[key] = settings[key]; } - let sidebar_pages = {} + let sidebar_pages = {}; for (let key in Settings.structure) { - sidebar_pages[key] = Settings.structure[key].name + sidebar_pages[key] = Settings.structure[key].name; } interface SettingsDialogVueData { - structure: any - profile: null | SettingsProfile - all_profiles: SettingsProfile[] - open_category: string - search_term: string + structure: any; + profile: null | SettingsProfile; + all_profiles: SettingsProfile[]; + open_category: string; + search_term: string; } Settings.dialog = new Dialog({ id: 'settings', @@ -133,8 +133,8 @@ onVueSetup(function () { page: 'general', actions: ['import_settings', 'export_settings'], onPageSwitch(page) { - Settings.dialog.content_vue.open_category = page - Settings.dialog.content_vue.search_term = '' + Settings.dialog.content_vue.open_category = page; + Settings.dialog.content_vue.search_term = ''; }, }, component: { @@ -145,11 +145,11 @@ onVueSetup(function () { all_profiles: SettingsProfile.all, open_category: 'general', search_term: '', - } as SettingsDialogVueData + } as SettingsDialogVueData; }, methods: { saveSettings(this: SettingsDialogVueData) { - Settings.saveLocalStorages() + Settings.saveLocalStorages(); }, settingContextMenu(setting: Setting, event: MouseEvent) { new Menu([ @@ -157,11 +157,11 @@ onVueSetup(function () { name: 'dialog.settings.reset_to_default', icon: 'replay', click: () => { - setting.ui_value = setting.default_value - this.saveSettings() + setting.ui_value = setting.default_value; + this.saveSettings(); }, }, - ]).open(event) + ]).open(event); }, showProfileMenu(this: SettingsDialogVueData) { let items: MenuItem[] = [ @@ -170,67 +170,67 @@ onVueSetup(function () { icon: 'remove', color: '', click: () => { - this.profile = null + this.profile = null; }, }, - ] + ]; SettingsProfile.all.forEach(profile => { items.push({ name: profile.name, icon: 'manage_accounts', color: markerColors[profile.color % markerColors.length].standard, click: () => { - this.profile = profile + this.profile = profile; if (profile.condition.type == 'selectable') { - profile.select() + profile.select(); } else { - SettingsProfile.unselect() + SettingsProfile.unselect(); } }, - }) - }) + }); + }); items.push('_', { name: 'dialog.settings.create_profile', icon: 'add', click: () => { - this.profile = new SettingsProfile({}) - this.profile.openDialog() + this.profile = new SettingsProfile({}); + this.profile.openDialog(); }, - }) + }); // @ts-ignore - new Menu('settings_profiles', items).open(this.$refs.profile_menu) + new Menu('settings_profiles', items).open(this.$refs.profile_menu); }, profileButtonPress(this: SettingsDialogVueData) { if (!this.profile) { - this.profile = new SettingsProfile({}) + this.profile = new SettingsProfile({}); } - this.profile.openDialog() + this.profile.openDialog(); }, getProfileValuesForSetting(this: SettingsDialogVueData, key) { return this.all_profiles.filter(profile => { - return profile.settings[key] !== undefined - }) + return profile.settings[key] !== undefined; + }); }, getProfileColor(profile?: SettingsProfile): string { if (profile && markerColors[profile.color % markerColors.length]) { - return markerColors[profile.color % markerColors.length].standard + return markerColors[profile.color % markerColors.length].standard; } - return '' + return ''; }, isFullWidth(setting: Setting): boolean { - return ['text', 'password', 'select'].includes(setting.type) + return ['text', 'password', 'select'].includes(setting.type); }, getPluginName(plugin_id: string): string { - let plugin = Plugins.all.find(p => p.id == plugin_id) - return plugin?.title ?? plugin_id + let plugin = Plugins.all.find(p => p.id == plugin_id); + return plugin?.title ?? plugin_id; }, revealPlugin(plugin_id: string) { - let plugin = Plugins.all.find(p => p.id == plugin_id) - if (!plugin) return + let plugin = Plugins.all.find(p => p.id == plugin_id); + if (!plugin) return; - Plugins.dialog.show() - Plugins.dialog.content_vue.selectPlugin!(plugin) + Plugins.dialog.show(); + Plugins.dialog.content_vue.selectPlugin!(plugin); }, getIconNode: Blockbench.getIconNode, tl, @@ -239,42 +239,42 @@ onVueSetup(function () { computed: { list() { if (this.search_term) { - var keywords = this.search_term.toLowerCase().replace(/_/g, ' ').split(' ') - var items = {} + var keywords = this.search_term.toLowerCase().replace(/_/g, ' ').split(' '); + var items = {}; for (var key in settings) { - var setting = settings[key] + var setting = settings[key]; if (Condition(setting.condition)) { - var name = setting.name.toLowerCase() - var desc = setting.description.toLowerCase() - var missmatch = false + var name = setting.name.toLowerCase(); + var desc = setting.description.toLowerCase(); + var missmatch = false; for (var word of keywords) { if ( !key.includes(word) && !name.includes(word) && !desc.includes(word) ) { - missmatch = true + missmatch = true; } } if (!missmatch) { - items[key] = setting + items[key] = setting; } } } - return items + return items; } else { - return this.structure[this.open_category].items + return this.structure[this.open_category].items; } }, title() { if (this.search_term) { - return tl('dialog.settings.search_results') + return tl('dialog.settings.search_results'); } else { - return this.structure[this.open_category].name + return this.structure[this.open_category].name; } }, profile_name() { - return this.profile ? this.profile.name : tl('generic.none') + return this.profile ? this.profile.name : tl('generic.none'); }, }, template: ` @@ -349,29 +349,29 @@ onVueSetup(function () { `, }, onButton() { - Settings.save() + Settings.save(); function hasSettingChanged(id) { - return Settings.old && settings[id].value !== Settings.old[id] + return Settings.old && settings[id].value !== Settings.old[id]; } - let changed_settings = [] + let changed_settings = []; for (let id in settings) { - let setting = settings[id] - if (!Condition(setting.condition)) continue - let has_changed = hasSettingChanged(id) + let setting = settings[id]; + if (!Condition(setting.condition)) continue; + let has_changed = hasSettingChanged(id); if (has_changed) { - changed_settings.push(setting) + changed_settings.push(setting); if (setting.onChange) { - setting.onChange(setting.value) + setting.onChange(setting.value); } if (isApp && setting.launch_setting) { - ipcRenderer.send('edit-launch-setting', { key: id, value: setting.value }) + ipcRenderer.send('edit-launch-setting', { key: id, value: setting.value }); } } } - let restart_settings = changed_settings.filter(setting => setting.requires_restart) + let restart_settings = changed_settings.filter(setting => setting.requires_restart); if (restart_settings.length) { - Settings.showRestartMessage(restart_settings) + Settings.showRestartMessage(restart_settings); } }, - }) -}) + }); +}); diff --git a/js/interface/themes.ts b/js/interface/themes.ts index 9d0b09022..93007653a 100644 --- a/js/interface/themes.ts +++ b/js/interface/themes.ts @@ -1,40 +1,40 @@ -import DarkTheme from '../../themes/dark.bbtheme' -import LightTheme from '../../themes/light.bbtheme' -import ContrastTheme from '../../themes/contrast.bbtheme' -import { compareVersions, patchedAtob } from '../util/util' -import { Dialog } from './dialog' -import { settings, Settings } from './settings' -import tinycolor from 'tinycolor2' -import { BBYaml } from '../util/yaml' -import { Blockbench } from '../api' -import { InputFormConfig } from './form' -import { Filesystem } from '../file_system' -import { fs } from '../native_apis' - -type ThemeSource = 'built_in' | 'file' | 'repository' | 'custom' +import DarkTheme from '../../themes/dark.bbtheme'; +import LightTheme from '../../themes/light.bbtheme'; +import ContrastTheme from '../../themes/contrast.bbtheme'; +import { compareVersions, patchedAtob } from '../util/util'; +import { Dialog } from './dialog'; +import { settings, Settings } from './settings'; +import tinycolor from 'tinycolor2'; +import { BBYaml } from '../util/yaml'; +import { Blockbench } from '../api'; +import { InputFormConfig } from './form'; +import { Filesystem } from '../file_system'; +import { fs } from '../native_apis'; + +type ThemeSource = 'built_in' | 'file' | 'repository' | 'custom'; type ThemeData = { - name: string - author: string - version?: string - borders?: boolean - thumbnail?: string - css: string - id: string - main_font?: string - headline_font?: string - code_font?: string - colors: Record - source?: ThemeSource - path?: string - desktop_only?: boolean + name: string; + author: string; + version?: string; + borders?: boolean; + thumbnail?: string; + css: string; + id: string; + main_font?: string; + headline_font?: string; + code_font?: string; + colors: Record; + source?: ThemeSource; + path?: string; + desktop_only?: boolean; options?: { [key: string]: { - name: string - options: Record - } - } - option_values: Record -} + name: string; + options: Record; + }; + }; + option_values: Record; +}; const DEFAULT_COLORS = { ui: '#1e2127', @@ -55,121 +55,123 @@ const DEFAULT_COLORS = { grid: '#495061', wireframe: '#576f82', checkerboard: '#14171b', -} +}; export class CustomTheme { - name: string - author: string - version?: string - borders: boolean - thumbnail?: string - css: string - id: string - main_font: string - headline_font: string - code_font: string - colors: Record - source?: 'built_in' | 'file' | 'repository' | 'custom' - path?: string - desktop_only?: boolean + name: string; + author: string; + version?: string; + borders: boolean; + thumbnail?: string; + css: string; + id: string; + main_font: string; + headline_font: string; + code_font: string; + colors: Record; + source?: 'built_in' | 'file' | 'repository' | 'custom'; + path?: string; + desktop_only?: boolean; options: null | { [key: string]: { - name: string - options: Record - } - } - option_values: Record + name: string; + options: Record; + }; + }; + option_values: Record; constructor(data?: Partial) { - this.id = '' - this.name = '' - this.author = '' - this.main_font = '' - this.headline_font = '' - this.code_font = '' - this.borders = false - this.thumbnail = '' - this.css = '' - this.colors = structuredClone(DEFAULT_COLORS) - this.options = null - this.option_values = {} + this.id = ''; + this.name = ''; + this.author = ''; + this.main_font = ''; + this.headline_font = ''; + this.code_font = ''; + this.borders = false; + this.thumbnail = ''; + this.css = ''; + this.colors = structuredClone(DEFAULT_COLORS); + this.options = null; + this.option_values = {}; if (data) { - this.extend(data) + this.extend(data); } } extend(data: Partial) { - Merge.string(this, data, 'id') - Merge.string(this, data, 'name') - Merge.string(this, data, 'author') - Merge.string(this, data, 'version') - Merge.string(this, data, 'source') - Merge.string(this, data, 'path') - Merge.boolean(this, data, 'desktop_only') - Merge.boolean(this, data, 'borders') - Merge.string(this, data, 'main_font') - Merge.string(this, data, 'headline_font') - Merge.string(this, data, 'code_font') - Merge.string(this, data, 'css') - Merge.string(this, data, 'thumbnail') + Merge.string(this, data, 'id'); + Merge.string(this, data, 'name'); + Merge.string(this, data, 'author'); + Merge.string(this, data, 'version'); + Merge.string(this, data, 'source'); + Merge.string(this, data, 'path'); + Merge.boolean(this, data, 'desktop_only'); + Merge.boolean(this, data, 'borders'); + Merge.string(this, data, 'main_font'); + Merge.string(this, data, 'headline_font'); + Merge.string(this, data, 'code_font'); + Merge.string(this, data, 'css'); + Merge.string(this, data, 'thumbnail'); if (data.colors) { for (let key in this.colors) { if (data.colors[key]) { - Merge.string(this.colors, data.colors, key) + Merge.string(this.colors, data.colors, key); } else { - CustomTheme.selected.colors[key] = DEFAULT_COLORS[key] + CustomTheme.selected.colors[key] = DEFAULT_COLORS[key]; } } } - if (data.options) this.options = structuredClone(data.options) - if (data.option_values) this.option_values = Object.assign({}, data.option_values) + if (data.options) this.options = structuredClone(data.options); + if (data.option_values) this.option_values = Object.assign({}, data.option_values); } openOptions() { - let form: InputFormConfig = {} - if (!this.options) return - if (CustomTheme.selected != this) this.load() - let theme = this + let form: InputFormConfig = {}; + if (!this.options) return; + if (CustomTheme.selected != this) this.load(); + let theme = this; for (let key in this.options) { - let opt = this.options[key] - let chars = Object.keys(opt.options).reduce((val, key) => val + opt.options[key].length) + let opt = this.options[key]; + let chars = Object.keys(opt.options).reduce( + (val, key) => val + opt.options[key].length + ); form[key] = { label: opt.name, type: chars.length > 28 ? 'select' : 'inline_select', options: opt.options, value: this.option_values[key], default: this.option_values[key], - } + }; } new Dialog('theme_configuration', { title: 'layout.theme.configure', form, singleButton: true, onFormChange(result: Record) { - theme.option_values = result - CustomTheme.updateSettings() - theme.save() + theme.option_values = result; + CustomTheme.updateSettings(); + theme.save(); }, - }).show() + }).show(); } - static selected: CustomTheme = new CustomTheme({ id: 'dark' }) + static selected: CustomTheme = new CustomTheme({ id: 'dark' }); static get data(): CustomTheme { - return CustomTheme.selected + return CustomTheme.selected; } - static backup_data: string | null = null + static backup_data: string | null = null; static themes: CustomTheme[] = [DarkTheme, LightTheme, ContrastTheme].map(theme_data => { - let theme = new CustomTheme().parseBBTheme(theme_data, true) - theme.source = 'built_in' - return theme - }) - static defaultColors = DEFAULT_COLORS - static sideloaded_themes: string[] = [] - static dialog: Dialog | null = null + let theme = new CustomTheme().parseBBTheme(theme_data, true); + theme.source = 'built_in'; + return theme; + }); + static defaultColors = DEFAULT_COLORS; + static sideloaded_themes: string[] = []; + static dialog: Dialog | null = null; static setup() { - fs - const theme_watchers: Record = {} - let remote_themes_loaded = false + fs; + const theme_watchers: Record = {}; + let remote_themes_loaded = false; CustomTheme.dialog = new Dialog({ id: 'theme', title: 'dialog.settings.theme', @@ -196,43 +198,47 @@ export class CustomTheme { name: 'layout.documentation', icon: 'fa-book', click() { - Blockbench.openLink('https://www.blockbench.net/wiki/blockbench/themes') + Blockbench.openLink( + 'https://www.blockbench.net/wiki/blockbench/themes' + ); }, }, 'import_theme', 'export_theme', ], onPageSwitch(page) { - CustomTheme.dialog.content_vue.open_category = page + CustomTheme.dialog.content_vue.open_category = page; if (page == 'color' && !CustomTheme.dialog_is_setup) { - CustomTheme.setupDialog() + CustomTheme.setupDialog(); } }, }, onOpen() { - this.content_vue.data = CustomTheme.data + this.content_vue.data = CustomTheme.data; if (!remote_themes_loaded) { - remote_themes_loaded = true + remote_themes_loaded = true; $.getJSON( 'https://api.github.com/repos/JannisX11/blockbench-themes/contents/themes' ) .then(files => { files.forEach(async file => { try { - let { content } = await $.getJSON(file.git_url) - let theme = new CustomTheme().parseBBTheme(patchedAtob(content)) - if (theme.desktop_only && Blockbench.isMobile) return false - theme.id = file.name.replace(/\.\w+/, '') - theme.source = 'repository' + let { content } = await $.getJSON(file.git_url); + let theme = new CustomTheme().parseBBTheme( + patchedAtob(content) + ); + if (theme.desktop_only && Blockbench.isMobile) return false; + theme.id = file.name.replace(/\.\w+/, ''); + theme.source = 'repository'; if (!CustomTheme.themes.find(t2 => t2.id == theme.id)) { - CustomTheme.themes.push(theme) + CustomTheme.themes.push(theme); } } catch (err) { - console.error(err) + console.error(err); } - }) + }); }) - .catch(console.error) + .catch(console.error); } }, component: { @@ -252,80 +258,80 @@ export class CustomTheme { }, watch: { 'data.main_font'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.headline_font'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.code_font'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.borders'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.css'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.thumbnail'() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, 'data.colors': { handler() { - CustomTheme.updateSettings() - CustomTheme.selected.save() + CustomTheme.updateSettings(); + CustomTheme.selected.save(); }, deep: true, }, }, methods: { selectTheme(theme: CustomTheme) { - theme.load() - this.data = theme - CustomTheme.selected.save() + theme.load(); + this.data = theme; + CustomTheme.selected.save(); }, loadBackup() { - let theme = new CustomTheme(JSON.parse(CustomTheme.backup_data)) - theme.load() - this.clearBackup() + let theme = new CustomTheme(JSON.parse(CustomTheme.backup_data)); + theme.load(); + this.clearBackup(); }, clearBackup() { - this.backup = '' - CustomTheme.backup_data = null + this.backup = ''; + CustomTheme.backup_data = null; }, customizeTheme() { - CustomTheme.customizeTheme() + CustomTheme.customizeTheme(); }, getThemeThumbnailStyle(theme: CustomTheme) { - let style = {} + let style = {}; for (let key in CustomTheme.defaultColors) { - style[`--color-${key}`] = CustomTheme.defaultColors[key] + style[`--color-${key}`] = CustomTheme.defaultColors[key]; } for (let key in theme.colors) { - style[`--color-${key}`] = theme.colors[key] + style[`--color-${key}`] = theme.colors[key]; } - return style + return style; }, openContextMenu(theme: CustomTheme, event: MouseEvent) { - if (theme.source != 'file') return - let selected = theme.id == this.data.id + if (theme.source != 'file') return; + let selected = theme.id == this.data.id; let menu = new Menu([ { name: 'menu.texture.folder', icon: 'folder', condition: isApp, click: () => { - if (!isApp || !theme.path) return + if (!isApp || !theme.path) return; if (!fs.existsSync(theme.path)) { - Blockbench.showQuickMessage('texture.error.file') - return + Blockbench.showQuickMessage('texture.error.file'); + return; } - Filesystem.showFileInFolder(theme.path) + Filesystem.showFileInFolder(theme.path); }, }, { @@ -334,24 +340,24 @@ export class CustomTheme { condition: isApp && selected, click: () => { if (theme_watchers[theme.path]) { - theme_watchers[theme.path].close() - delete theme_watchers[theme.path] + theme_watchers[theme.path].close(); + delete theme_watchers[theme.path]; } else if (fs.existsSync(theme.path)) { - let timeout: number = 0 + let timeout: number = 0; theme_watchers[theme.path] = fs.watch( theme.path, eventType => { if (eventType == 'change') { if (timeout) { - clearTimeout(timeout) - timeout = 0 + clearTimeout(timeout); + timeout = 0; } timeout = window.setTimeout(() => { - theme.reloadThemeFile() - }, 60) + theme.reloadThemeFile(); + }, 60); } } - ) + ); } }, }, @@ -360,33 +366,33 @@ export class CustomTheme { icon: 'refresh', condition: isApp && selected, click: () => { - theme.reloadThemeFile() + theme.reloadThemeFile(); }, }, { name: 'generic.remove', icon: 'clear', click: () => { - this.themes.remove(theme) - CustomTheme.sideloaded_themes.remove(theme.path) + this.themes.remove(theme); + CustomTheme.sideloaded_themes.remove(theme.path); localStorage.setItem( 'themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes) - ) + ); }, }, - ]) - menu.open(event) + ]); + menu.open(event); }, tl, }, computed: { listed_themes() { - let themes = this.themes.slice() + let themes = this.themes.slice(); if (this.data.source == 'custom') { - themes.splice(0, 0, this.data) + themes.splice(0, 0, this.data); } - return themes + return themes; }, }, template: ` @@ -525,17 +531,17 @@ export class CustomTheme { `, }, onButton() { - Settings.save() + Settings.save(); }, - }) + }); } static setupDialog() { - let wrapper = $('#color_wrapper') + let wrapper = $('#color_wrapper'); for (let key in CustomTheme.defaultColors) { - let scope_key = key - let hex = CustomTheme.selected.colors[scope_key] - let last_color = hex - let field = wrapper.find(`#color_field_${scope_key} .layout_color_preview`) + let scope_key = key; + let hex = CustomTheme.selected.colors[scope_key]; + let last_color = hex; + let field = wrapper.find(`#color_field_${scope_key} .layout_color_preview`); field.spectrum({ preferredFormat: 'hex', @@ -548,344 +554,350 @@ export class CustomTheme { cancelText: tl('dialog.cancel'), chooseText: tl('dialog.confirm'), move(c) { - CustomTheme.selected.colors[scope_key] = c.toHexString() - CustomTheme.customizeTheme() + CustomTheme.selected.colors[scope_key] = c.toHexString(); + CustomTheme.customizeTheme(); }, change(c) { - last_color = c.toHexString() + last_color = c.toHexString(); }, hide(c) { - CustomTheme.selected.colors[scope_key] = last_color - field.spectrum('set', last_color) + CustomTheme.selected.colors[scope_key] = last_color; + field.spectrum('set', last_color); }, beforeShow() { - last_color = CustomTheme.selected.colors[scope_key] - field.spectrum('set', last_color) + last_color = CustomTheme.selected.colors[scope_key]; + field.spectrum('set', last_color); }, - }) + }); } - CustomTheme.dialog_is_setup = true + CustomTheme.dialog_is_setup = true; } - static dialog_is_setup = false + static dialog_is_setup = false; static customizeTheme() { if (CustomTheme.selected.source != 'custom') { - let theme = new CustomTheme(CustomTheme.selected) + let theme = new CustomTheme(CustomTheme.selected); theme.extend({ name: theme.name ? 'Copy of ' + theme.name : 'Custom Theme', author: settings.username.value as string, id: 'custom_theme', source: 'custom', - }) - let i = 0 + }); + let i = 0; while (CustomTheme.themes.find(t2 => theme.id == t2.id)) { - i++ - theme.id = 'custom_theme_' + i + i++; + theme.id = 'custom_theme_' + i; } - if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = theme - theme.load() + if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = theme; + theme.load(); } } static updateColors() { - $('meta[name=theme-color]').attr('content', CustomTheme.selected.colors.frame) + $('meta[name=theme-color]').attr('content', CustomTheme.selected.colors.frame); document.body.classList.toggle( 'light_mode', new tinycolor(CustomTheme.selected.colors.ui).isLight() - ) + ); - let gizmo_colors = Canvas.gizmo_colors + let gizmo_colors = Canvas.gizmo_colors; if (typeof gizmo_colors != 'undefined') { - let preview_style = window.getComputedStyle(document.getElementById('preview')) + let preview_style = window.getComputedStyle(document.getElementById('preview')); function update(three_color, variable) { - let string = preview_style.getPropertyValue(variable).trim() - three_color.set(string) + let string = preview_style.getPropertyValue(variable).trim(); + three_color.set(string); } - update(gizmo_colors.r, '--color-axis-x') - update(gizmo_colors.g, '--color-axis-y') - update(gizmo_colors.b, '--color-axis-z') - update(gizmo_colors.grid, '--color-grid') - update(gizmo_colors.u, '--color-axis-u') // spline space colors - update(gizmo_colors.v, '--color-axis-v') // spline space colors - update(gizmo_colors.w, '--color-axis-w') // spline space colors - update(Canvas.gridMaterial.color, '--color-grid') - update(Canvas.wireframeMaterial.color, '--color-wireframe') - update(gizmo_colors.solid, '--color-solid') - update(gizmo_colors.outline, '--color-outline') - update(gizmo_colors.gizmo_hover, '--color-gizmohover') - update(Canvas.outlineMaterial.color, '--color-outline') + update(gizmo_colors.r, '--color-axis-x'); + update(gizmo_colors.g, '--color-axis-y'); + update(gizmo_colors.b, '--color-axis-z'); + update(gizmo_colors.grid, '--color-grid'); + update(gizmo_colors.u, '--color-axis-u'); // spline space colors + update(gizmo_colors.v, '--color-axis-v'); // spline space colors + update(gizmo_colors.w, '--color-axis-w'); // spline space colors + update(Canvas.gridMaterial.color, '--color-grid'); + update(Canvas.wireframeMaterial.color, '--color-wireframe'); + update(gizmo_colors.solid, '--color-solid'); + update(gizmo_colors.outline, '--color-outline'); + update(gizmo_colors.gizmo_hover, '--color-gizmohover'); + update(Canvas.outlineMaterial.color, '--color-outline'); update( (Canvas.ground_plane.material as THREE.MeshBasicMaterial).color, '--color-ground' - ) + ); update( (Canvas.brush_outline.material as THREE.ShaderMaterial).uniforms.color.value, '--color-brush-outline' - ) - update(gizmo_colors.spline_handle_aligned, '--color-spline-handle-aligned') - update(gizmo_colors.spline_handle_mirrored, '--color-spline-handle-mirrored') - update(gizmo_colors.spline_handle_free, '--color-spline-handle-free') + ); + update(gizmo_colors.spline_handle_aligned, '--color-spline-handle-aligned'); + update(gizmo_colors.spline_handle_mirrored, '--color-spline-handle-mirrored'); + update(gizmo_colors.spline_handle_free, '--color-spline-handle-free'); Canvas.pivot_marker.children.forEach(c => { // @ts-ignore - c.updateColors() - }) + c.updateColors(); + }); } } static updateSettings() { - let theme = CustomTheme.selected - let variables = {} + let theme = CustomTheme.selected; + let variables = {}; for (let key in CustomTheme.selected.colors) { - variables['--color-' + key] = CustomTheme.selected.colors[key] + variables['--color-' + key] = CustomTheme.selected.colors[key]; } - variables['--font-custom-main'] = `'${theme.main_font}'` - variables['--font-custom-headline'] = `'${theme.headline_font}'` - variables['--font-custom-code'] = `'${theme.code_font}'` - let variable_section = `body {\n` + variables['--font-custom-main'] = `'${theme.main_font}'`; + variables['--font-custom-headline'] = `'${theme.headline_font}'`; + variables['--font-custom-code'] = `'${theme.code_font}'`; + let variable_section = `body {\n`; for (let key in variables) { - variable_section += `\n\t${key}: ${variables[key]};` + variable_section += `\n\t${key}: ${variables[key]};`; } - variable_section += '\n}\n' + variable_section += '\n}\n'; document.getElementById('theme_css').textContent = - `@layer theme {${variable_section}${theme.css}};` - document.body.classList.toggle('theme_borders', !!theme.borders) + `@layer theme {${variable_section}${theme.css}};`; + document.body.classList.toggle('theme_borders', !!theme.borders); // Options for (let attribute of document.body.attributes) { if ( attribute.name.startsWith('theme-') && (!theme.options || theme.options[attribute.name.substring(6)] == undefined) ) { - document.body.removeAttribute(attribute.name) + document.body.removeAttribute(attribute.name); } } for (let key in theme.options ?? {}) { - if (theme.option_values[key] == undefined) continue - document.body.setAttribute('theme-' + key, theme.option_values[key]) + if (theme.option_values[key] == undefined) continue; + document.body.setAttribute('theme-' + key, theme.option_values[key]); } - CustomTheme.loadThumbnailStyles() - CustomTheme.updateColors() + CustomTheme.loadThumbnailStyles(); + CustomTheme.updateColors(); } static loadThumbnailStyles() { // @ts-ignore let split_regex = isApp || window.chrome ? new RegExp('(? `[theme_id="${theme.id}"] ${e.trim()}`) - .join(', ') - thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n` + .join(', '); + thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; } } if (CustomTheme.selected.source == 'custom') { - style.textContent = CustomTheme.selected.thumbnail - const sheet = style.sheet + style.textContent = CustomTheme.selected.thumbnail; + const sheet = style.sheet; for (const rule of sheet.cssRules) { - if (!(rule as CSSStyleRule).selectorText) continue + if (!(rule as CSSStyleRule).selectorText) continue; const selector = (rule as CSSStyleRule).selectorText .split(split_regex) .map(e => `[theme_id="${CustomTheme.selected.id}"] ${e.trim()}`) - .join(', ') - thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n` + .join(', '); + thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; } } - document.head.removeChild(style) - document.getElementById('theme_thumbnail_css').textContent = thumbnailStyles + document.head.removeChild(style); + document.getElementById('theme_thumbnail_css').textContent = thumbnailStyles; } load() { if (CustomTheme.selected.source == 'custom' && CustomTheme.selected.name) { // Backup - if (!CustomTheme.dialog.content_vue) CustomTheme.dialog.build() - CustomTheme.dialog.content_vue.backup = CustomTheme.selected.name - CustomTheme.backup_data = JSON.stringify(CustomTheme.selected) + if (!CustomTheme.dialog.content_vue) CustomTheme.dialog.build(); + CustomTheme.dialog.content_vue.backup = CustomTheme.selected.name; + CustomTheme.backup_data = JSON.stringify(CustomTheme.selected); } - CustomTheme.selected = this - if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = this + CustomTheme.selected = this; + if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = this; - CustomTheme.updateSettings() - this.save() + CustomTheme.updateSettings(); + this.save(); } static loadTheme(theme: CustomTheme) { - theme.load() + theme.load(); } reloadThemeFile() { - let content = fs.readFileSync(this.path, { encoding: 'utf8' }) - if (!content) return - let new_theme = new CustomTheme().parseBBTheme(content) - delete new_theme.id - delete new_theme.option_values - this.extend(new_theme) - this.load() + let content = fs.readFileSync(this.path, { encoding: 'utf8' }); + if (!content) return; + let new_theme = new CustomTheme().parseBBTheme(content); + delete new_theme.id; + delete new_theme.option_values; + this.extend(new_theme); + this.load(); } static import(file: Filesystem.FileResult) { - let content = file.content as string + let content = file.content as string; - let theme = new CustomTheme().parseBBTheme(content) + let theme = new CustomTheme().parseBBTheme(content); - theme.id = file.name.replace(/\.\w+$/, '') - if (!theme.name) theme.name = theme.id + theme.id = file.name.replace(/\.\w+$/, ''); + if (!theme.name) theme.name = theme.id; - theme.source = 'file' - theme.path = file.path + theme.source = 'file'; + theme.path = file.path; - CustomTheme.loadTheme(theme) - CustomTheme.themes.remove(CustomTheme.themes.find((t2: CustomTheme) => t2.id == theme.id)) - CustomTheme.themes.push(theme) + CustomTheme.loadTheme(theme); + CustomTheme.themes.remove(CustomTheme.themes.find((t2: CustomTheme) => t2.id == theme.id)); + CustomTheme.themes.push(theme); if (isApp) { - CustomTheme.sideloaded_themes.safePush(file.path) - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)) + CustomTheme.sideloaded_themes.safePush(file.path); + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ); } } parseBBTheme(content: string, include_id?: boolean): this { if (content.startsWith('{')) { // Lecagy format - let json_data = JSON.parse(content) - this.extend(json_data) + let json_data = JSON.parse(content); + this.extend(json_data); } else { function extractSection(start_match: string, end_match: string): string | undefined { - content = content.trim() - if (content.startsWith(start_match) == false) return - let end = content.indexOf(end_match) - let section = content.substring(start_match.length, end) - content = content.substring(end + end_match.length) - return section + content = content.trim(); + if (content.startsWith(start_match) == false) return; + let end = content.indexOf(end_match); + let section = content.substring(start_match.length, end); + content = content.substring(end + end_match.length); + return section; } - let metadata_section = extractSection('/*', '*/') + let metadata_section = extractSection('/*', '*/'); if (metadata_section) { - let metadata = BBYaml.parse(metadata_section) - this.extend(metadata) + let metadata = BBYaml.parse(metadata_section); + this.extend(metadata); } - let variable_section = extractSection('body {', '}') + let variable_section = extractSection('body {', '}'); if (variable_section) { for (let line of variable_section.split(/\r?\n/)) { - line = line.trim() + line = line.trim(); if (line.startsWith('--color')) { - let [key, value] = line.replace('--color-', '').split(/:\s*/) - this.colors[key] = value.replace(/;/, '') + let [key, value] = line.replace('--color-', '').split(/:\s*/); + this.colors[key] = value.replace(/;/, ''); } else if (line.startsWith('--font-custom')) { - let [key, value] = line.replace('--font-custom-', '').split(/:\s*/) - value = value.replace(';', '') + let [key, value] = line.replace('--font-custom-', '').split(/:\s*/); + value = value.replace(';', ''); switch (key) { case 'main': - this.main_font = value - break + this.main_font = value; + break; case 'headline': - this.headline_font = value - break + this.headline_font = value; + break; case 'code': - this.code_font = value - break + this.code_font = value; + break; } } } } - this.thumbnail = extractSection('@scope (thumbnail) {', '\n}') ?? '' + this.thumbnail = extractSection('@scope (thumbnail) {', '\n}') ?? ''; - this.css = content + this.css = content; } - return this + return this; } compileBBTheme(): string { - let theme = '/*' + let theme = '/*'; let metadata = { name: this.name, author: this.author, version: this.version, borders: this.borders, - } + }; for (let key in metadata) { - if (metadata[key] == undefined) continue - theme += `\n${key}: ${metadata[key].toString()}` + if (metadata[key] == undefined) continue; + theme += `\n${key}: ${metadata[key].toString()}`; } - theme += '\n*/\n' + theme += '\n*/\n'; // Variables - theme += 'body {' + theme += 'body {'; for (let color in this.colors) { - let color_value = this.colors[color] - theme += `\n\t--color-${color}: ${color_value};` + let color_value = this.colors[color]; + theme += `\n\t--color-${color}: ${color_value};`; } - if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};` - if (this.headline_font) theme += `\n\t--font-custom-headline: ${this.headline_font};` - if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};` + if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};`; + if (this.headline_font) theme += `\n\t--font-custom-headline: ${this.headline_font};`; + if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};`; - theme += '\n}\n' + theme += '\n}\n'; if (this.thumbnail) { - theme += '@scope (thumbnail) {\n' - theme += this.thumbnail.replace(/\n/g, '\n\t') - theme += '\n}\n' + theme += '@scope (thumbnail) {\n'; + theme += this.thumbnail.replace(/\n/g, '\n\t'); + theme += '\n}\n'; } if (this.css) { - theme += this.css + theme += this.css; } - return theme + return theme; } save() { - localStorage.setItem('theme', JSON.stringify(this)) + localStorage.setItem('theme', JSON.stringify(this)); } } if (isApp && localStorage.getItem('themes_sideloaded')) { try { - let sideloaded = JSON.parse(localStorage.getItem('themes_sideloaded')) + let sideloaded = JSON.parse(localStorage.getItem('themes_sideloaded')); if (sideloaded instanceof Array && sideloaded.length) { - CustomTheme.sideloaded_themes = sideloaded + CustomTheme.sideloaded_themes = sideloaded; CustomTheme.sideloaded_themes.forEachReverse(path => { if (!fs.existsSync(path)) { - CustomTheme.sideloaded_themes.remove(path) + CustomTheme.sideloaded_themes.remove(path); } - }) - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)) + }); + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ); Blockbench.read(CustomTheme.sideloaded_themes, { errorbox: false }, files => { files.forEach(file => { - let id = file.name.replace(/\.\w+$/, '') - let theme = new CustomTheme().parseBBTheme(file.content as string) - theme.id = id - if (!theme.name) theme.name = theme.id - theme.source = 'file' - theme.path = file.path - CustomTheme.themes.push(theme) - }) - }) + let id = file.name.replace(/\.\w+$/, ''); + let theme = new CustomTheme().parseBBTheme(file.content as string); + theme.id = id; + if (!theme.name) theme.name = theme.id; + theme.source = 'file'; + theme.path = file.path; + CustomTheme.themes.push(theme); + }); + }); } } catch (err) {} - CustomTheme.loadThumbnailStyles() + CustomTheme.loadThumbnailStyles(); } export function loadThemes() { - let stored_theme_data: ThemeData | undefined + let stored_theme_data: ThemeData | undefined; try { if (localStorage.getItem('theme')) { - stored_theme_data = JSON.parse(localStorage.getItem('theme')) + stored_theme_data = JSON.parse(localStorage.getItem('theme')); } } catch (err) {} if (stored_theme_data) { - let stored_theme = new CustomTheme(stored_theme_data) + let stored_theme = new CustomTheme(stored_theme_data); // Check for updates if (stored_theme.source == 'repository' && stored_theme.id) { - CustomTheme.loadTheme(stored_theme) + CustomTheme.loadTheme(stored_theme); fetch( `https://cdn.jsdelivr.net/gh/JannisX11/blockbench-themes/themes/${stored_theme.id}.bbtheme` ).then(async result => { - let text_content = await result.text() - if (!text_content) return - let theme = new CustomTheme().parseBBTheme(text_content) + let text_content = await result.text(); + if (!text_content) return; + let theme = new CustomTheme().parseBBTheme(text_content); if ( (theme.version && !stored_theme.version) || @@ -894,23 +906,23 @@ export function loadThemes() { compareVersions(theme.version, stored_theme.version)) ) { // Update theme - stored_theme.extend(theme) - stored_theme.source = 'repository' - stored_theme.load() - console.log(`Updated Theme "${stored_theme.id}" to v${theme.version}`) + stored_theme.extend(theme); + stored_theme.source = 'repository'; + stored_theme.load(); + console.log(`Updated Theme "${stored_theme.id}" to v${theme.version}`); } - }) + }); } else if ( (stored_theme.source == 'built_in' || stored_theme.source == 'file') && stored_theme.id ) { - let match = CustomTheme.themes.find(t => t.id == stored_theme.id) + let match = CustomTheme.themes.find(t => t.id == stored_theme.id); if (match) { - match.option_values = stored_theme.option_values - match.load() + match.option_values = stored_theme.option_values; + match.load(); } } else if (stored_theme.source == 'custom') { - CustomTheme.loadTheme(stored_theme) + CustomTheme.loadTheme(stored_theme); } } } @@ -921,9 +933,9 @@ BARS.defineActions(function () { icon: 'style', category: 'blockbench', click: function () { - CustomTheme.dialog.show() + CustomTheme.dialog.show(); }, - }) + }); new Action('import_theme', { icon: 'folder', category: 'blockbench', @@ -935,30 +947,30 @@ BARS.defineActions(function () { type: 'Blockbench Theme', }, function (files) { - CustomTheme.import(files[0]) + CustomTheme.import(files[0]); } - ) + ); }, - }) + }); new Action('export_theme', { icon: 'style', category: 'blockbench', click: function () { - let id = CustomTheme.selected.id || '' - let content = CustomTheme.selected.compileBBTheme() + let id = CustomTheme.selected.id || ''; + let content = CustomTheme.selected.compileBBTheme(); Blockbench.export({ resource_id: 'config', type: 'Blockbench Theme', extensions: ['bbtheme'], name: id, content, - }) + }); }, - }) - BarItems.import_theme.toElement('#layout_title_bar') - BarItems.export_theme.toElement('#layout_title_bar') -}) + }); + BarItems.import_theme.toElement('#layout_title_bar'); + BarItems.export_theme.toElement('#layout_title_bar'); +}); Object.assign(window, { CustomTheme, -}) +}); diff --git a/js/io/format.ts b/js/io/format.ts index 1c6ad4256..02cceb967 100644 --- a/js/io/format.ts +++ b/js/io/format.ts @@ -1,28 +1,28 @@ -import { Vue } from '../lib/libs' -import { Blockbench } from '../api' -import { setProjectTitle } from '../interface/interface' -import { settings, Settings } from '../interface/settings' -import { TickUpdates } from '../misc' -import { Mode, Modes } from '../modes' -import { Group } from '../outliner/group' -import { Canvas } from '../preview/canvas' -import { DefaultCameraPresets } from '../preview/preview' -import { Property } from '../util/property' -import { SplineMesh } from '../outliner/spline_mesh' +import { Vue } from '../lib/libs'; +import { Blockbench } from '../api'; +import { setProjectTitle } from '../interface/interface'; +import { settings, Settings } from '../interface/settings'; +import { TickUpdates } from '../misc'; +import { Mode, Modes } from '../modes'; +import { Group } from '../outliner/group'; +import { Canvas } from '../preview/canvas'; +import { DefaultCameraPresets } from '../preview/preview'; +import { Property } from '../util/property'; +import { SplineMesh } from '../outliner/spline_mesh'; interface FormatPage { - component?: Vue.Component + component?: Vue.Component; content?: ( | { - type?: 'image' | 'h2' | 'h3' | 'h4' | 'text' | 'label' | 'image' | '' - text?: string - source?: string - width?: number - height?: number + type?: 'image' | 'h2' | 'h3' | 'h4' | 'text' | 'label' | 'image' | ''; + text?: string; + source?: string; + width?: number; + height?: number; } | string - )[] - button_text?: string + )[]; + button_text?: string; } interface CubeSizeLimiter { /** @@ -31,11 +31,11 @@ interface CubeSizeLimiter { test: ( cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number } - ) => boolean + ) => boolean; /** * Move the cube back into the restructions */ - move(cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }): void + move(cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }): void; /** * Clamp the cube to fit into the restrictions. When an axis and direction is provided, clamp the element on that side to prevent wandering. */ @@ -44,487 +44,487 @@ interface CubeSizeLimiter { values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }, axis?: number, direction?: boolean | null - ) => void + ) => void; /** * Set to true to tell Blockbench to check and adjust the cube limit after rotating a cube */ - rotation_affected?: boolean + rotation_affected?: boolean; /** * Optionally set the coordinate limits of cubes in local space */ - coordinate_limits?: [number, number] + coordinate_limits?: [number, number]; } /** * The current format */ -declare const Format: ModelFormat +declare const Format: ModelFormat; -export const Formats = {} +export const Formats = {}; Object.defineProperty(window, 'Format', { get() { - return Blockbench.Format + return Blockbench.Format; }, -}) +}); //Formats interface FormatOptions { - id: string - icon: string - name?: string - description?: string - category?: string - target?: string | string[] - confidential?: boolean - condition?: ConditionResolvable - show_on_start_screen?: boolean - can_convert_to?: boolean - format_page?: FormatPage - onFormatPage?(): void - onStart?(): void - onSetup?(project: ModelProject, newModel?: boolean): void - convertTo?(): void - new?(): boolean + id: string; + icon: string; + name?: string; + description?: string; + category?: string; + target?: string | string[]; + confidential?: boolean; + condition?: ConditionResolvable; + show_on_start_screen?: boolean; + can_convert_to?: boolean; + format_page?: FormatPage; + onFormatPage?(): void; + onStart?(): void; + onSetup?(project: ModelProject, newModel?: boolean): void; + convertTo?(): void; + new?(): boolean; /** * Enables Box UV on cubes by default */ - box_uv: boolean + box_uv: boolean; /** * If true, box UV is optional and can be toggled on the project or per cube */ - optional_box_uv: boolean + optional_box_uv: boolean; /** * If true, only one texture can be assigned to the model at a time, instead of textures being assigned per face */ - single_texture: boolean + single_texture: boolean; /** * If true, a single texture is used as default, but textures can still be assigned to faces */ - single_texture_default: boolean + single_texture_default: boolean; /** * If true, textures can be assigned per group instead of per face */ - per_group_texture: boolean + per_group_texture: boolean; /** * If true, UV size (the size of the texture in UV space) will be defined per texture and not per project */ - per_texture_uv_size: boolean + per_texture_uv_size: boolean; /** * Enable a model identifier field in the project settings. Default is true */ - model_identifier: boolean + model_identifier: boolean; /** * If true, the file name of a project will be editable in the project settings */ - legacy_editable_file_name: boolean + legacy_editable_file_name: boolean; /** * If true, enables a field in the project settings to set a parent model ID */ - parent_model_id: boolean + parent_model_id: boolean; /** * Adds a toggle in the project settings to enable project wide vertex color ambient occlusion */ - vertex_color_ambient_occlusion: boolean + vertex_color_ambient_occlusion: boolean; /** * Enable flipbook animated textures */ - animated_textures: boolean + animated_textures: boolean; /** * Enable groups to work as bones and rig the model */ - bone_rig: boolean + bone_rig: boolean; /** * Enable armatures to rig meshes */ - armature_rig: boolean + armature_rig: boolean; /** * Align the grid center with the model origin, instead of the grid corner */ - centered_grid: boolean + centered_grid: boolean; /** * Add the ability to rotate cubes */ - rotate_cubes: boolean + rotate_cubes: boolean; /** * Add the ability to stretch cubes. Stretch scales cubes from the center without affecting UV */ - stretch_cubes: boolean + stretch_cubes: boolean; /** * If true, cube sizes are limited to integer values */ - integer_size: boolean + integer_size: boolean; /** * Enable mesh elements */ - meshes: boolean + meshes: boolean; /** * Enable spline elements */ - splines: boolean + splines: boolean; /** * Enable texture meshes */ - texture_meshes: boolean + texture_meshes: boolean; /** * Enable billboard elements */ - billboards: boolean + billboards: boolean; /** * Enable locators */ - locators: boolean + locators: boolean; /** * Enable PBR texture materials yay */ - pbr: boolean + pbr: boolean; /** * Enforces a rotation limit for cubes of up to 45 degrees in either direction and one axis at a time */ - rotation_limit: boolean + rotation_limit: boolean; /** * Forces cube rotations to snap to 22.5 degree increments */ - rotation_snap: boolean + rotation_snap: boolean; /** * Allows cube UVs to be rotated */ - uv_rotation: boolean + uv_rotation: boolean; /** * Enables Minecraft Java block/item model specific cube face features (tint and export) */ - java_face_properties: boolean + java_face_properties: boolean; /** * Allows assigning one texture to be used as a texture for particles related to the model */ - select_texture_for_particles: boolean + select_texture_for_particles: boolean; /** * Enable mcmeta files for animated texture files */ - texture_mcmeta: boolean + texture_mcmeta: boolean; /** * Enables an option to set an expression for bone bindings */ - bone_binding_expression: boolean + bone_binding_expression: boolean; /** * If true, animations will be saved into files */ - animation_files: boolean + animation_files: boolean; /** * Enables a folder path per texture that can be set in the texture properties window */ - texture_folder: boolean + texture_folder: boolean; /** * Enables the 2D image editor */ - image_editor: boolean + image_editor: boolean; /** * Enables edit mode. Default is true */ - edit_mode: boolean + edit_mode: boolean; /** * Enables paint mode. Default is true */ - paint_mode: boolean + paint_mode: boolean; /** * Enables display mode */ - display_mode: boolean + display_mode: boolean; /** * Emaböes animation mode */ - animation_mode: boolean + animation_mode: boolean; /** * Enables pose mode */ - pose_mode: boolean + pose_mode: boolean; /** * Enables animation controllers */ - animation_controllers: boolean + animation_controllers: boolean; /** * If true, cube sizes will not be floored to calculate UV sizes with box UV. This can result in UVs not aligning with pixel edges */ - box_uv_float_size: boolean + box_uv_float_size: boolean; /** * Enables properties for Minecraft Java block/item models related to block shading (shading option and light emission value) */ - java_cube_shading_properties: boolean + java_cube_shading_properties: boolean; /** * Enables cullfaces, the ability on faces in Minecraft block models to set a direction, that, if covered by another block, will cause the face to unrender */ - cullfaces: boolean + cullfaces: boolean; /** * A set of characters that is allowed in node names (names of elements and groups that can be referenced externally, this does not apply to cubes or meshes) */ - node_name_regex: string + node_name_regex: string; /** * Set the default render sides for textures */ - render_sides: 'front' | 'double' | 'back' | (() => 'front' | 'double' | 'back') + render_sides: 'front' | 'double' | 'back' | (() => 'front' | 'double' | 'back'); /** * Options to limit the size of cubes */ - cube_size_limiter?: CubeSizeLimiter + cube_size_limiter?: CubeSizeLimiter; - codec?: Codec - onActivation?(): void - onDeactivation?(): void + codec?: Codec; + onActivation?(): void; + onDeactivation?(): void; } export interface ModelFormat extends FormatOptions {} export class ModelFormat implements FormatOptions { - id: string - icon: string - name: string - description: string - category: string - target: string | string[] - confidential: boolean - condition: ConditionResolvable - show_on_start_screen: boolean - show_in_new_list: boolean - can_convert_to: boolean - plugin: string - format_page?: FormatPage - onFormatPage?(): void - onStart?(): void - onSetup?(project: ModelProject, newModel?: boolean): void - - cube_size_limiter?: CubeSizeLimiter - - codec?: Codec - onActivation?(): void - onDeactivation?(): void - - static properties: Record + id: string; + icon: string; + name: string; + description: string; + category: string; + target: string | string[]; + confidential: boolean; + condition: ConditionResolvable; + show_on_start_screen: boolean; + show_in_new_list: boolean; + can_convert_to: boolean; + plugin: string; + format_page?: FormatPage; + onFormatPage?(): void; + onStart?(): void; + onSetup?(project: ModelProject, newModel?: boolean): void; + + cube_size_limiter?: CubeSizeLimiter; + + codec?: Codec; + onActivation?(): void; + onDeactivation?(): void; + + static properties: Record; constructor(id: string, data: Partial) { if (typeof id == 'object') { - data = id - id = data.id + data = id; + id = data.id; } - Formats[id] = this - this.id = id - this.name = data.name || tl('format.' + this.id) - this.description = data.description || tl('format.' + this.id + '.desc') - if (this.description == 'format.' + this.id + '.desc') this.description = '' - this.category = data.category || 'other' - this.target = data.target - this.show_on_start_screen = true - this.confidential = false - this.can_convert_to = true + Formats[id] = this; + this.id = id; + this.name = data.name || tl('format.' + this.id); + this.description = data.description || tl('format.' + this.id + '.desc'); + if (this.description == 'format.' + this.id + '.desc') this.description = ''; + this.category = data.category || 'other'; + this.target = data.target; + this.show_on_start_screen = true; + this.confidential = false; + this.can_convert_to = true; for (let id in ModelFormat.properties) { - ModelFormat.properties[id].reset(this) + ModelFormat.properties[id].reset(this); } - this.render_sides = data.render_sides - this.cube_size_limiter = data.cube_size_limiter - - this.codec = data.codec - this.onSetup = data.onSetup - this.onFormatPage = data.onFormatPage - this.onActivation = data.onActivation - this.onDeactivation = data.onDeactivation - this.format_page = data.format_page - Merge.string(this, data, 'icon') - Merge.boolean(this, data, 'show_on_start_screen') - Merge.boolean(this, data, 'show_in_new_list') - Merge.boolean(this, data, 'can_convert_to') - Merge.boolean(this, data, 'confidential') - - if (data.new) this.new = data.new + this.render_sides = data.render_sides; + this.cube_size_limiter = data.cube_size_limiter; + + this.codec = data.codec; + this.onSetup = data.onSetup; + this.onFormatPage = data.onFormatPage; + this.onActivation = data.onActivation; + this.onDeactivation = data.onDeactivation; + this.format_page = data.format_page; + Merge.string(this, data, 'icon'); + Merge.boolean(this, data, 'show_on_start_screen'); + Merge.boolean(this, data, 'show_in_new_list'); + Merge.boolean(this, data, 'can_convert_to'); + Merge.boolean(this, data, 'confidential'); + + if (data.new) this.new = data.new; if (data.rotation_limit && data.rotation_snap === undefined) { - data.rotation_snap = true + data.rotation_snap = true; } for (let id in ModelFormat.properties) { - ModelFormat.properties[id].merge(this, data) + ModelFormat.properties[id].merge(this, data); } if (this.format_page && this.format_page.component) { - Vue.component(`format_page_${this.id}`, this.format_page.component) + Vue.component(`format_page_${this.id}`, this.format_page.component); } - Blockbench.dispatchEvent('construct_format', { format: this }) + Blockbench.dispatchEvent('construct_format', { format: this }); } select() { if (Format && typeof Format.onDeactivation == 'function') { - Format.onDeactivation() + Format.onDeactivation(); } // @ts-ignore Incompatible internal and external types - Blockbench.Format = Blockbench.Project.format = this + Blockbench.Format = Blockbench.Project.format = this; if (typeof this.onActivation == 'function') { - Format.onActivation() + Format.onActivation(); } - Canvas.buildGrid() + Canvas.buildGrid(); if (Format.centered_grid) { - scene.position.set(0, 0, 0) - Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 8 + scene.position.set(0, 0, 0); + Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 8; } else { - scene.position.set(-8, 0, -8) - Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 0 + scene.position.set(-8, 0, -8); + Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 0; } PreviewModel.getActiveModels().forEach(model => { - model.update() - }) - Settings.updateSettingsInProfiles() + model.update(); + }); + Settings.updateSettingsInProfiles(); Preview.all.forEach(preview => { if (preview.isOrtho && typeof preview.angle == 'number') { // @ts-ignore Incompatible internal and external types - preview.loadAnglePreset(DefaultCameraPresets[preview.angle + 1] as AnglePreset) + preview.loadAnglePreset(DefaultCameraPresets[preview.angle + 1] as AnglePreset); } - }) + }); if (Mode.selected && !Condition(Mode.selected.condition)) { - ;(this.pose_mode ? Modes.options.paint : Modes.options.edit).select() + (this.pose_mode ? Modes.options.paint : Modes.options.edit).select(); } - Interface.Panels.animations.inside_vue.$data.animation_files_enabled = this.animation_files + Interface.Panels.animations.inside_vue.$data.animation_files_enabled = this.animation_files; // @ts-ignore - Interface.status_bar.vue.Format = this - UVEditor.vue.cube_uv_rotation = this.uv_rotation - if (Modes.vue) Modes.vue.$forceUpdate() - TickUpdates.interface = true - Canvas.updateShading() - Canvas.updateRenderSides() - Blockbench.dispatchEvent('select_format', { format: this, project: Project }) - return this + Interface.status_bar.vue.Format = this; + UVEditor.vue.cube_uv_rotation = this.uv_rotation; + if (Modes.vue) Modes.vue.$forceUpdate(); + TickUpdates.interface = true; + Canvas.updateShading(); + Canvas.updateRenderSides(); + Blockbench.dispatchEvent('select_format', { format: this, project: Project }); + return this; } new(): boolean { // @ts-ignore Conflicting internal and external types if (newProject(this)) { - ;(BarItems.project_window as Action).click() - return true + (BarItems.project_window as Action).click(); + return true; } - return false + return false; } convertTo() { - Undo.history.empty() - Undo.index = 0 - Project.export_path = '' - Project.unhandled_root_fields = {} + Undo.history.empty(); + Undo.index = 0; + Project.export_path = ''; + Project.unhandled_root_fields = {}; - var old_format = Blockbench.Format as ModelFormat - this.select() - Modes.options.edit.select() + var old_format = Blockbench.Format as ModelFormat; + this.select(); + Modes.options.edit.select(); // Box UV if (!this.optional_box_uv) { - Project.box_uv = this.box_uv + Project.box_uv = this.box_uv; Cube.all.forEach(cube => { - cube.setUVMode(this.box_uv) - }) + cube.setUVMode(this.box_uv); + }); } if (!this.per_texture_uv_size && old_format.per_texture_uv_size) { - let tex = Texture.getDefault() + let tex = Texture.getDefault(); if (tex) { - Project.texture_width = tex.uv_width - Project.texture_height = tex.uv_height + Project.texture_width = tex.uv_width; + Project.texture_height = tex.uv_height; } } if (this.per_texture_uv_size && !old_format.per_texture_uv_size) { Texture.all.forEach(tex => { - tex.uv_width = Project.texture_width - tex.uv_height = Project.texture_height - }) + tex.uv_width = Project.texture_width; + tex.uv_height = Project.texture_height; + }); } //Bone Rig if (!this.bone_rig && old_format.bone_rig) { Group.all.forEach(group => { - group.rotation.V3_set(0, 0, 0) - }) + group.rotation.V3_set(0, 0, 0); + }); } if (this.bone_rig && !old_format.bone_rig) { - var loose_stuff = [] + var loose_stuff = []; Outliner.root.forEach(el => { if (el instanceof Group == false) { - loose_stuff.push(el) + loose_stuff.push(el); } - }) + }); if (loose_stuff.length) { - var root_group = new Group().init().addTo() + var root_group = new Group().init().addTo(); loose_stuff.forEach(el => { - el.addTo(root_group) - }) + el.addTo(root_group); + }); } // @ts-ignore if (!Project.geometry_name && Project.name) { // @ts-ignore - Project.geometry_name = Project.name + Project.geometry_name = Project.name; } } if (this.bone_rig) { Group.all.forEach(group => { - group.createUniqueName() - }) + group.createUniqueName(); + }); } if (this.centered_grid != old_format.centered_grid) { - let offset = this.centered_grid ? -8 : 8 + let offset = this.centered_grid ? -8 : 8; Cube.all.forEach(cube => { for (let axis of [0, 2]) { - cube.from[axis] += offset - cube.to[axis] += offset - cube.origin[axis] += offset + cube.from[axis] += offset; + cube.to[axis] += offset; + cube.origin[axis] += offset; } - }) + }); Group.all.forEach(group => { - group.origin[0] += offset - group.origin[2] += offset - }) + group.origin[0] += offset; + group.origin[2] += offset; + }); } if (!this.single_texture && old_format.single_texture && Texture.all.length) { - let texture = Texture.getDefault() + let texture = Texture.getDefault(); Outliner.elements .filter((el: OutlinerElement) => 'applyTexture' in el) .forEach(el => { // @ts-ignore - el.applyTexture(texture, true) - }) + el.applyTexture(texture, true); + }); } //Rotate Cubes if (!this.rotate_cubes && old_format.rotate_cubes) { Cube.all.forEach(cube => { - cube.rotation.V3_set(0, 0, 0) - }) + cube.rotation.V3_set(0, 0, 0); + }); } //Meshes if (!this.meshes && old_format.meshes) { Mesh.all.slice().forEach(mesh => { - mesh.remove() - }) + mesh.remove(); + }); } //Splines if (!this.splines && old_format.splines) { SplineMesh.all.slice().forEach(spline => { - spline.remove() - }) + spline.remove(); + }); } //Locators if (!this.locators && old_format.locators) { Locator.all.slice().forEach(locator => { - locator.remove() - }) + locator.remove(); + }); } //Texture Meshes if (!this.texture_meshes && old_format.texture_meshes) { TextureMesh.all.slice().forEach(tm => { - tm.remove() - }) + tm.remove(); + }); } //Billboards if (!this.billboards && old_format.billboards) { // @ts-ignore Billboard.all.slice().forEach(b => { - b.remove() - }) + b.remove(); + }); } //Canvas Limit @@ -534,100 +534,100 @@ export class ModelFormat implements FormatOptions { !settings.deactivate_size_limit.value ) { Cube.all.forEach(cube => { - this.cube_size_limiter.move(cube) - }) + this.cube_size_limiter.move(cube); + }); Cube.all.forEach(cube => { - this.cube_size_limiter.clamp(cube) - }) + this.cube_size_limiter.clamp(cube); + }); } //Rotation Limit if (this.rotation_limit && !old_format.rotation_limit && this.rotate_cubes) { Cube.all.forEach(cube => { if (!cube.rotation.allEqual(0)) { - var axis = getAxisNumber(cube.rotationAxis()) || 0 + var axis = getAxisNumber(cube.rotationAxis()) || 0; var cube_rotation = this.rotation_snap ? Math.round(cube.rotation[axis] / 22.5) * 22.5 - : cube.rotation[axis] - var angle = limitNumber(cube_rotation, -45, 45) - cube.rotation.V3_set(0, 0, 0) - cube.rotation[axis] = angle + : cube.rotation[axis]; + var angle = limitNumber(cube_rotation, -45, 45); + cube.rotation.V3_set(0, 0, 0); + cube.rotation[axis] = angle; } - }) + }); } //Animation Mode if (!this.animation_mode && old_format.animation_mode) { - Animator.animations.length = 0 + Animator.animations.length = 0; } - Project.saved = false - setProjectTitle() + Project.saved = false; + setProjectTitle(); - Blockbench.dispatchEvent('convert_format', { format: this, old_format }) + Blockbench.dispatchEvent('convert_format', { format: this, old_format }); if (typeof this.onSetup == 'function') { - this.onSetup(Project) + this.onSetup(Project); } - Canvas.updateAllPositions() - Canvas.updateAllBones() - Canvas.updateAllFaces() - updateSelection() + Canvas.updateAllPositions(); + Canvas.updateAllBones(); + Canvas.updateAllFaces(); + updateSelection(); } delete() { - delete Formats[this.id] + delete Formats[this.id]; // @ts-ignore - if (this.codec && this.codec.format == this) delete this.codec.format - Blockbench.dispatchEvent('delete_format', { format: this }) + if (this.codec && this.codec.format == this) delete this.codec.format; + Blockbench.dispatchEvent('delete_format', { format: this }); } } -new Property(ModelFormat, 'string', 'node_name_regex') -new Property(ModelFormat, 'boolean', 'box_uv') -new Property(ModelFormat, 'boolean', 'optional_box_uv') -new Property(ModelFormat, 'boolean', 'box_uv_float_size') -new Property(ModelFormat, 'boolean', 'single_texture') -new Property(ModelFormat, 'boolean', 'single_texture_default') -new Property(ModelFormat, 'boolean', 'per_group_texture') -new Property(ModelFormat, 'boolean', 'per_texture_uv_size') -new Property(ModelFormat, 'boolean', 'model_identifier', { default: true }) -new Property(ModelFormat, 'boolean', 'legacy_editable_file_name') -new Property(ModelFormat, 'boolean', 'parent_model_id') -new Property(ModelFormat, 'boolean', 'vertex_color_ambient_occlusion') -new Property(ModelFormat, 'boolean', 'animated_textures') -new Property(ModelFormat, 'boolean', 'bone_rig') -new Property(ModelFormat, 'boolean', 'armature_rig') -new Property(ModelFormat, 'boolean', 'centered_grid') -new Property(ModelFormat, 'boolean', 'rotate_cubes') -new Property(ModelFormat, 'boolean', 'stretch_cubes') -new Property(ModelFormat, 'boolean', 'integer_size') -new Property(ModelFormat, 'boolean', 'meshes') -new Property(ModelFormat, 'boolean', 'splines') -new Property(ModelFormat, 'boolean', 'texture_meshes') -new Property(ModelFormat, 'boolean', 'billboards') -new Property(ModelFormat, 'boolean', 'locators') -new Property(ModelFormat, 'boolean', 'rotation_limit') -new Property(ModelFormat, 'boolean', 'rotation_snap') -new Property(ModelFormat, 'boolean', 'uv_rotation') -new Property(ModelFormat, 'boolean', 'java_cube_shading_properties') -new Property(ModelFormat, 'boolean', 'java_face_properties') -new Property(ModelFormat, 'boolean', 'cullfaces') -new Property(ModelFormat, 'boolean', 'select_texture_for_particles') -new Property(ModelFormat, 'boolean', 'texture_mcmeta') -new Property(ModelFormat, 'boolean', 'bone_binding_expression') -new Property(ModelFormat, 'boolean', 'animation_files') -new Property(ModelFormat, 'boolean', 'animation_controllers') -new Property(ModelFormat, 'boolean', 'image_editor') -new Property(ModelFormat, 'boolean', 'edit_mode', { default: true }) -new Property(ModelFormat, 'boolean', 'paint_mode', { default: true }) -new Property(ModelFormat, 'boolean', 'pose_mode') -new Property(ModelFormat, 'boolean', 'display_mode') -new Property(ModelFormat, 'boolean', 'animation_mode') -new Property(ModelFormat, 'boolean', 'texture_folder') -new Property(ModelFormat, 'boolean', 'pbr') +new Property(ModelFormat, 'string', 'node_name_regex'); +new Property(ModelFormat, 'boolean', 'box_uv'); +new Property(ModelFormat, 'boolean', 'optional_box_uv'); +new Property(ModelFormat, 'boolean', 'box_uv_float_size'); +new Property(ModelFormat, 'boolean', 'single_texture'); +new Property(ModelFormat, 'boolean', 'single_texture_default'); +new Property(ModelFormat, 'boolean', 'per_group_texture'); +new Property(ModelFormat, 'boolean', 'per_texture_uv_size'); +new Property(ModelFormat, 'boolean', 'model_identifier', { default: true }); +new Property(ModelFormat, 'boolean', 'legacy_editable_file_name'); +new Property(ModelFormat, 'boolean', 'parent_model_id'); +new Property(ModelFormat, 'boolean', 'vertex_color_ambient_occlusion'); +new Property(ModelFormat, 'boolean', 'animated_textures'); +new Property(ModelFormat, 'boolean', 'bone_rig'); +new Property(ModelFormat, 'boolean', 'armature_rig'); +new Property(ModelFormat, 'boolean', 'centered_grid'); +new Property(ModelFormat, 'boolean', 'rotate_cubes'); +new Property(ModelFormat, 'boolean', 'stretch_cubes'); +new Property(ModelFormat, 'boolean', 'integer_size'); +new Property(ModelFormat, 'boolean', 'meshes'); +new Property(ModelFormat, 'boolean', 'splines'); +new Property(ModelFormat, 'boolean', 'texture_meshes'); +new Property(ModelFormat, 'boolean', 'billboards'); +new Property(ModelFormat, 'boolean', 'locators'); +new Property(ModelFormat, 'boolean', 'rotation_limit'); +new Property(ModelFormat, 'boolean', 'rotation_snap'); +new Property(ModelFormat, 'boolean', 'uv_rotation'); +new Property(ModelFormat, 'boolean', 'java_cube_shading_properties'); +new Property(ModelFormat, 'boolean', 'java_face_properties'); +new Property(ModelFormat, 'boolean', 'cullfaces'); +new Property(ModelFormat, 'boolean', 'select_texture_for_particles'); +new Property(ModelFormat, 'boolean', 'texture_mcmeta'); +new Property(ModelFormat, 'boolean', 'bone_binding_expression'); +new Property(ModelFormat, 'boolean', 'animation_files'); +new Property(ModelFormat, 'boolean', 'animation_controllers'); +new Property(ModelFormat, 'boolean', 'image_editor'); +new Property(ModelFormat, 'boolean', 'edit_mode', { default: true }); +new Property(ModelFormat, 'boolean', 'paint_mode', { default: true }); +new Property(ModelFormat, 'boolean', 'pose_mode'); +new Property(ModelFormat, 'boolean', 'display_mode'); +new Property(ModelFormat, 'boolean', 'animation_mode'); +new Property(ModelFormat, 'boolean', 'texture_folder'); +new Property(ModelFormat, 'boolean', 'pbr'); Object.assign(window, { ModelFormat, Formats, -}) +}); diff --git a/js/io/formats/fbx.ts b/js/io/formats/fbx.ts index 14391de73..23199163a 100644 --- a/js/io/formats/fbx.ts +++ b/js/io/formats/fbx.ts @@ -1,19 +1,19 @@ -import { Blockbench } from '../../api' -import { Filesystem } from '../../file_system' -import { Armature } from '../../outliner/armature' -import { adjustFromAndToForInflateAndStretch } from '../../outliner/cube' -import { patchedAtob } from '../../util/util' -import { JSZip, THREE } from './../../lib/libs' +import { Blockbench } from '../../api'; +import { Filesystem } from '../../file_system'; +import { Armature } from '../../outliner/armature'; +import { adjustFromAndToForInflateAndStretch } from '../../outliner/cube'; +import { patchedAtob } from '../../util/util'; +import { JSZip, THREE } from './../../lib/libs'; -const _FBX_VERSION = 7300 +const _FBX_VERSION = 7300; -type TNumType = 'I' | 'D' | 'F' | 'L' | 'C' | 'Y' +type TNumType = 'I' | 'D' | 'F' | 'L' | 'C' | 'Y'; type TNumVal = { - type: TNumType - value: number | BigInt - isTNum: true - toString: () => string -} + type: TNumType; + value: number | BigInt; + isTNum: true; + toString: () => string; +}; /** * Wraps a number to include the type * @param {TNumType} type @@ -25,7 +25,7 @@ function TNum(type: TNumType, value: number | BigInt): TNumVal { value, isTNum: true, toString: () => 'key' + value.toString(), - } + }; } function printAttributeList(list, type = 'i', key = 'a') { @@ -33,25 +33,25 @@ function printAttributeList(list, type = 'i', key = 'a') { _values: [`_*${list.length}`], _type: type, [key]: list, - } + }; } type FBXNode = { - _key?: string - _values: (string | number | TNumVal)[] - [key: string]: any -} + _key?: string; + _values: (string | number | TNumVal)[]; + [key: string]: any; +}; var codec = new Codec('fbx', { name: 'FBX Model', extension: 'fbx', support_partial_export: true, compile(options) { - options = Object.assign(this.getExportOptions(), options) - let scope = this - let export_scale = (options.scale || 16) / 100 - let model = [] - let armature_scale_const = new THREE.Vector3(1, 1, 1) + options = Object.assign(this.getExportOptions(), options); + let scope = this; + let export_scale = (options.scale || 16) / 100; + let model = []; + let armature_scale_const = new THREE.Vector3(1, 1, 1); model.push( [ '; FBX 7.3.0 project file', @@ -61,52 +61,52 @@ var codec = new Codec('fbx', { '', '', ].join('\n') - ) + ); function formatFBXComment(comment) { return ( '\n; ' + comment.split(/\n/g).join('\n; ') + '\n;------------------------------------------------------------------\n\n' - ) + ); } - let UUIDMap = {} + let UUIDMap = {}; function getID(uuid: string | number): TNumVal { - if (uuid == 0) return TNum('L', 0) - if (UUIDMap[uuid]) return UUIDMap[uuid] - let string_array = [] + if (uuid == 0) return TNum('L', 0); + if (UUIDMap[uuid]) return UUIDMap[uuid]; + let string_array = []; for (let i = 0; i < 8; i++) { - string_array.push(Math.floor(Math.random() * 10)) + string_array.push(Math.floor(Math.random() * 10)); } - string_array[0] = '7' - let s = string_array.join('') - UUIDMap[uuid] = TNum('L', parseInt(s)) - return UUIDMap[uuid] + string_array[0] = '7'; + let s = string_array.join(''); + UUIDMap[uuid] = TNum('L', parseInt(s)); + return UUIDMap[uuid]; } - let UniqueNames = {} + let UniqueNames = {}; function getUniqueName(namespace: string, uuid: string, original_name: string): string { - if (!UniqueNames[namespace]) UniqueNames[namespace] = {} - let names = UniqueNames[namespace] - if (names[uuid]) return names[uuid] + if (!UniqueNames[namespace]) UniqueNames[namespace] = {}; + let names = UniqueNames[namespace]; + if (names[uuid]) return names[uuid]; - let existing_names = Object.values(names) + let existing_names = Object.values(names); if (!existing_names.includes(original_name)) { - names[uuid] = original_name - return names[uuid] + names[uuid] = original_name; + return names[uuid]; } - let i = 1 + let i = 1; while (existing_names.includes(original_name + '_' + i)) { - i++ + i++; } - names[uuid] = original_name + '_' + i - return names[uuid] + names[uuid] = original_name + '_' + i; + return names[uuid]; } // FBXHeaderExtension - let date = new Date() - let dateString = date.toISOString().replace('T', ' ').replace('.', ':').replace('Z', '') - let model_url = 'C:\\Users\\Blockbench\\foobar.fbx' + let date = new Date(); + let dateString = date.toISOString().replace('T', ' ').replace('.', ':').replace('Z', ''); + let model_url = 'C:\\Users\\Blockbench\\foobar.fbx'; model.push({ FBXHeaderExtension: { FBXHeaderVersion: 1003, @@ -241,7 +241,7 @@ var codec = new Codec('fbx', { FileId: 'iVFoobar', CreationTime: dateString, Creator: Settings.get('credit'), - }) + }); model.push({ GlobalSettings: { @@ -294,11 +294,11 @@ var codec = new Codec('fbx', { }, }, }, - }) + }); // Documents Description - model.push(formatFBXComment('Documents Description')) - const uuid = BigInt(Math.floor(Math.random() * 2147483647) + 1) + model.push(formatFBXComment('Documents Description')); + const uuid = BigInt(Math.floor(Math.random() * 2147483647) + 1); model.push({ Documents: { Count: 1, @@ -312,13 +312,13 @@ var codec = new Codec('fbx', { RootNode: 0, }, }, - }) + }); // Document References - model.push(formatFBXComment('Document References')) + model.push(formatFBXComment('Document References')); model.push({ References: {}, - }) + }); let DefinitionCounter = { node_attributes: 0, @@ -333,28 +333,28 @@ var codec = new Codec('fbx', { animation_layer: 0, animation_curve_node: 0, animation_curve: 0, - } - let Objects: Record = {} + }; + let Objects: Record = {}; let Connections: { - name: string[] - id: (string | TNumVal)[] - property?: string - }[] = [] + name: string[]; + id: (string | TNumVal)[]; + property?: string; + }[] = []; let Takes = { Current: '', - } - let root = { name: 'RootNode', uuid: 0 } + }; + let root = { name: 'RootNode', uuid: 0 }; function getElementPos(element) { - let arr = element.origin.slice() + let arr = element.origin.slice(); if (element.parent instanceof Group) { - arr.V3_subtract(element.parent.origin) + arr.V3_subtract(element.parent.origin); } - return arr.V3_divide(export_scale) + return arr.V3_divide(export_scale); } function addNodeBase(node, fbx_type) { - let unique_name = getUniqueName('object', node.uuid, node.name) - let rotation_order = node.mesh.rotation.order == 'XYZ' ? 5 : 0 + let unique_name = getUniqueName('object', node.uuid, node.name); + let rotation_order = node.mesh.rotation.order == 'XYZ' ? 5 : 0; Objects['key' + node.uuid] = { _key: 'Model', _values: [getID(node.uuid), `Model::${unique_name}`, fbx_type], @@ -419,88 +419,88 @@ var codec = new Codec('fbx', { }, Shading: true, Culling: 'CullingOff', - } - let parent = node.parent == 'root' ? root : node.parent + }; + let parent = node.parent == 'root' ? root : node.parent; Connections.push({ name: [ `Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`, ], id: [getID(node.uuid), getID(parent.uuid)], - }) - DefinitionCounter.model++ - return Objects['key' + node.uuid] + }); + DefinitionCounter.model++; + return Objects['key' + node.uuid]; } // Groups Group.all.forEach(group => { - if (!group.export) return - addNodeBase(group, 'Null') - }) + if (!group.export) return; + addNodeBase(group, 'Null'); + }); // Locators + Null Objects - ;[...Locator.all, ...NullObject.all].forEach(group => { - if (!group.export) return - addNodeBase(group, 'Null') - }) + [...Locator.all, ...NullObject.all].forEach(group => { + if (!group.export) return; + addNodeBase(group, 'Null'); + }); // Meshes Mesh.all.forEach(mesh => { - if (!mesh.export) return - addNodeBase(mesh, 'Mesh') - let unique_name = getUniqueName('object', mesh.uuid, mesh.name) + if (!mesh.export) return; + addNodeBase(mesh, 'Mesh'); + let unique_name = getUniqueName('object', mesh.uuid, mesh.name); // Geometry - let positions = [] - let normals = [] - let uv = [] - let vertex_keys = [] - let indices = [] - let mesh_normals = mesh.calculateNormals() + let positions = []; + let normals = []; + let uv = []; + let vertex_keys = []; + let indices = []; + let mesh_normals = mesh.calculateNormals(); function addPosition(x, y, z) { - positions.push(x / export_scale, y / export_scale, z / export_scale) + positions.push(x / export_scale, y / export_scale, z / export_scale); } for (let vkey in mesh.vertices) { - addPosition(...mesh.vertices[vkey]) - vertex_keys.push(vkey) + addPosition(...mesh.vertices[vkey]); + vertex_keys.push(vkey); if (mesh.smooth_shading == true) { - normals.push(...mesh_normals[vkey]) + normals.push(...mesh_normals[vkey]); } } - let textures = [] + let textures = []; for (let key in mesh.faces) { if (mesh.faces[key].vertices.length >= 3) { - let face = mesh.faces[key] - let vertices = face.getSortedVertices() - let tex = mesh.faces[key].getTexture() - textures.push(tex) + let face = mesh.faces[key]; + let vertices = face.getSortedVertices(); + let tex = mesh.faces[key].getTexture(); + textures.push(tex); vertices.forEach(vkey => { uv.push( face.uv[vkey][0] / Project.getUVWidth(tex), 1 - face.uv[vkey][1] / Project.getUVHeight(tex) - ) - }) + ); + }); if (mesh.smooth_shading == false) { - normals.push(...face.getNormal(true)) + normals.push(...face.getNormal(true)); } vertices.forEach((vkey, vi) => { - let index = vertex_keys.indexOf(vkey) - if (vi + 1 == vertices.length) index = -1 - index - indices.push(index) - }) + let index = vertex_keys.indexOf(vkey); + if (vi + 1 == vertices.length) index = -1 - index; + indices.push(index); + }); } } - DefinitionCounter.geometry++ + DefinitionCounter.geometry++; - let used_textures = Texture.all.filter(t => textures.includes(t)) + let used_textures = Texture.all.filter(t => textures.includes(t)); - let geo_id = getID(mesh.uuid + '_geo') + let geo_id = getID(mesh.uuid + '_geo'); let geometry: FBXNode = { _key: 'Geometry', _values: [geo_id, `Geometry::${unique_name}`, 'Mesh'], @@ -586,13 +586,13 @@ var codec = new Codec('fbx', { TypedIndex: 0, }, }, - } - Objects[geo_id.toString()] = geometry + }; + Objects[geo_id.toString()] = geometry; Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], id: [geo_id, getID(mesh.uuid)], - }) + }); used_textures.forEach(tex => { Connections.push({ name: [ @@ -600,24 +600,24 @@ var codec = new Codec('fbx', { `Model::${unique_name}`, ], id: [getID(tex.uuid + '_m'), getID(mesh.uuid)], - }) - }) - }) + }); + }); + }); Armature.all.forEach(armature => { - let mesh = Mesh.all.find(m => m.getArmature() == armature) - let armature_name = getUniqueName('armature', armature.uuid, armature.name) - DefinitionCounter.pose++ + let mesh = Mesh.all.find(m => m.getArmature() == armature); + let armature_name = getUniqueName('armature', armature.uuid, armature.name); + DefinitionCounter.pose++; // Armature - let armature_attribute_id = getID(armature.uuid + '_attribute') + let armature_attribute_id = getID(armature.uuid + '_attribute'); Objects[armature_attribute_id.toString()] = { _key: 'NodeAttribute', _values: [armature_attribute_id, `NodeAttribute::${armature_name}`, 'Null'], TypeFlags: 'Null', - } - DefinitionCounter.node_attributes++ + }; + DefinitionCounter.node_attributes++; - let armature_id = getID(armature.uuid) + let armature_id = getID(armature.uuid); Objects[armature_id.toString()] = { _key: 'Model', _values: [armature_id, `Model::${armature_name}`, 'Null'], @@ -657,26 +657,26 @@ var codec = new Codec('fbx', { }, }, Culling: 'CullingOff', - } - let parent = armature.parent == 'root' ? root : armature.parent + }; + let parent = armature.parent == 'root' ? root : armature.parent; Connections.push({ name: [ `Model::${armature_name}`, `Model::${getUniqueName('object', parent.uuid as string, parent.name)}`, ], id: [getID(armature.uuid), getID(parent.uuid)], - }) - DefinitionCounter.model++ + }); + DefinitionCounter.model++; Connections.push({ name: [`NodeAttribute::${armature_name}`, `Model::${armature_name}`], id: [armature_attribute_id, armature_id], - }) + }); // Bind pose - let pose: FBXNode + let pose: FBXNode; if (mesh) { - let pose_id = getID(mesh.uuid + '_bind_pose') + let pose_id = getID(mesh.uuid + '_bind_pose'); pose = { _key: 'Pose', _values: [ @@ -687,40 +687,40 @@ var codec = new Codec('fbx', { Type: 'BindPose', Version: 100, NbPoseNodes: 0, - } + }; // Mesh bind pose - let matrix = new THREE.Matrix4() - matrix.scale(armature_scale_const) + let matrix = new THREE.Matrix4(); + matrix.scale(armature_scale_const); pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(mesh.uuid), Matrix: printAttributeList(matrix.elements, 'd'), - } - pose.NbPoseNodes++ + }; + pose.NbPoseNodes++; // Armature bind pose - let matrix2 = new THREE.Matrix4() - matrix.scale(armature_scale_const) + let matrix2 = new THREE.Matrix4(); + matrix.scale(armature_scale_const); pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(armature.uuid), Matrix: printAttributeList(matrix2.elements, 'd'), - } - pose.NbPoseNodes++ + }; + pose.NbPoseNodes++; - Objects[pose_id.toString()] = pose - DefinitionCounter.pose++ + Objects[pose_id.toString()] = pose; + DefinitionCounter.pose++; } // Bones - const bone_list = [] - const bind_matrix_list = [] + const bone_list = []; + const bind_matrix_list = []; function processBone(bone) { - console.log(bone) - bone_list.push(bone) - let unique_name = getUniqueName('bone', bone.uuid, bone.name) - let attribute_id = getID(bone.uuid + '_attribute') + console.log(bone); + bone_list.push(bone); + let unique_name = getUniqueName('bone', bone.uuid, bone.name); + let attribute_id = getID(bone.uuid + '_attribute'); Objects[attribute_id.toString()] = { _key: 'NodeAttribute', _values: [attribute_id, `NodeAttribute::${unique_name}`, 'LimbNode'], @@ -737,10 +737,10 @@ var codec = new Codec('fbx', { }, }, TypeFlags: 'Skeleton', - } - DefinitionCounter.node_attributes++ + }; + DefinitionCounter.node_attributes++; - let object_id = getID(bone.uuid) + let object_id = getID(bone.uuid); Objects[object_id.toString()] = { _key: 'Model', _values: [object_id, `Model::${unique_name}`, 'LimbNode'], @@ -774,25 +774,25 @@ var codec = new Codec('fbx', { //P4: {_key: 'P', _values: ["RotationOrder", "enum", "", "", rotation_order]}, }, Culling: 'CullingOff', - } - let parent = bone.parent == 'root' ? root : bone.parent + }; + let parent = bone.parent == 'root' ? root : bone.parent; Connections.push({ name: [ `Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`, ], id: [getID(bone.uuid), getID(parent.uuid)], - }) - DefinitionCounter.model++ + }); + DefinitionCounter.model++; Connections.push({ name: [`NodeAttribute::${unique_name}`, `Model::${unique_name}`], id: [attribute_id, object_id], - }) + }); if (bone.children.length == 0) { // End bone node - let attribute_id_end = getID(bone.uuid + '_end_attribute') + let attribute_id_end = getID(bone.uuid + '_end_attribute'); Objects[attribute_id_end.toString()] = { _key: 'NodeAttribute', _values: [ @@ -813,10 +813,10 @@ var codec = new Codec('fbx', { }, }, TypeFlags: 'Skeleton', - } - DefinitionCounter.node_attributes++ + }; + DefinitionCounter.node_attributes++; - let object_id_end = getID(bone.uuid + '_end') + let object_id_end = getID(bone.uuid + '_end'); Objects[object_id_end.toString()] = { _key: 'Model', _values: [object_id_end, `Model::${unique_name}_end`, 'LimbNode'], @@ -841,73 +841,73 @@ var codec = new Codec('fbx', { }, }, Culling: 'CullingOff', - } + }; Connections.push({ name: [`Model::${unique_name}_end`, `Model::${unique_name}`], id: [object_id_end, getID(bone.uuid)], - }) - DefinitionCounter.model++ + }); + DefinitionCounter.model++; Connections.push({ name: [`NodeAttribute::${unique_name}_end`, `Model::${unique_name}_end`], id: [attribute_id_end, object_id_end], - }) + }); } // Bind pose if (mesh) { - let matrix = new THREE.Matrix4().copy(bone.mesh.inverse_bind_matrix).invert() - matrix.scale(armature_scale_const) + let matrix = new THREE.Matrix4().copy(bone.mesh.inverse_bind_matrix).invert(); + matrix.scale(armature_scale_const); pose['PoseNode' + object_id] = { _key: 'PoseNode', Node: object_id, Matrix: printAttributeList(matrix.elements, 'd'), - } - pose.NbPoseNodes++ - bind_matrix_list.push(matrix) + }; + pose.NbPoseNodes++; + bind_matrix_list.push(matrix); } // Children for (let child of bone.children) { - processBone(child) + processBone(child); } } for (let child of armature.children) { - processBone(child) + processBone(child); } // Mesh deformers if (mesh) { - let deformer_id = getID(armature.uuid + '_deformer') + let deformer_id = getID(armature.uuid + '_deformer'); Objects[deformer_id.toString()] = { _key: 'Deformer', _values: [deformer_id, `Deformer::${armature_name}`, 'Skin'], Version: 101, Link_DeformAcuracy: 50, - } - DefinitionCounter.deformer++ + }; + DefinitionCounter.deformer++; Connections.push({ name: [ `Deformer::${armature_name}`, `Geometry::${getUniqueName('object', mesh.uuid, mesh.name)}`, ], id: [deformer_id, getID(mesh.uuid + '_geo')], - }) + }); - let mesh_transform = new THREE.Matrix4().copy(mesh.mesh.matrixWorld) - let vertex_keys = Object.keys(mesh.vertices) + let mesh_transform = new THREE.Matrix4().copy(mesh.mesh.matrixWorld); + let vertex_keys = Object.keys(mesh.vertices); for (let bone of bone_list) { - let sub_deformer_id = getID(bone.uuid + '_deformer') - let bone_name = getUniqueName('bone', bone.uuid, bone.name) - let indices = [] - let weights = [] + let sub_deformer_id = getID(bone.uuid + '_deformer'); + let bone_name = getUniqueName('bone', bone.uuid, bone.name); + let indices = []; + let weights = []; for (let vkey in bone.vertex_weights) { if (bone.vertex_weights[vkey] > 0.001) { - indices.push(vertex_keys.indexOf(vkey)) - weights.push(Math.clamp(bone.vertex_weights[vkey], 0, 1)) + indices.push(vertex_keys.indexOf(vkey)); + weights.push(Math.clamp(bone.vertex_weights[vkey], 0, 1)); } } - let bind_matrix = bind_matrix_list[bone_list.indexOf(bone)] + let bind_matrix = bind_matrix_list[bone_list.indexOf(bone)]; Objects[sub_deformer_id.toString()] = { _key: 'Deformer', @@ -918,23 +918,23 @@ var codec = new Codec('fbx', { Weights: printAttributeList(weights, 'd'), Transform: printAttributeList(mesh_transform.elements, 'd'), TransformLink: printAttributeList(bind_matrix.elements, 'd'), - } - DefinitionCounter.deformer++ + }; + DefinitionCounter.deformer++; Connections.push({ name: [`SubDeformer::${bone_name}`, `Deformer::${armature_name}`], id: [sub_deformer_id, deformer_id], - }) + }); Connections.push({ name: [ `Model::${getUniqueName('bone', bone.uuid, bone.name)}`, `SubDeformer::${bone_name}`, ], id: [getID(bone.uuid), sub_deformer_id], - }) + }); } } - }) + }); // Cubes const cube_face_normals = { @@ -944,47 +944,47 @@ var codec = new Codec('fbx', { west: [-1, 0, 0], up: [0, 1, 0], down: [0, -1, 0], - } + }; Cube.all.forEach(cube => { - if (!cube.export) return - addNodeBase(cube, 'Mesh') - let unique_name = getUniqueName('object', cube.uuid, cube.name) + if (!cube.export) return; + addNodeBase(cube, 'Mesh'); + let unique_name = getUniqueName('object', cube.uuid, cube.name); // Geometry - let positions = [] - let normals = [] - let uv = [] - let indices = [] + let positions = []; + let normals = []; + let uv = []; + let indices = []; function addPosition(x, y, z) { positions.push( (x - cube.origin[0]) / export_scale, (y - cube.origin[1]) / export_scale, (z - cube.origin[2]) / export_scale - ) + ); } - var adjustedFrom = cube.from.slice() - var adjustedTo = cube.to.slice() - adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, cube) + var adjustedFrom = cube.from.slice(); + var adjustedTo = cube.to.slice(); + adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, cube); - addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2]) - addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]) - addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2]) - addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]) - addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]) - addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2]) - addPosition(adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]) - addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2]) + addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2]); + addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]); + addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2]); + addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]); + addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]); + addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2]); + addPosition(adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]); + addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2]); - let textures = [] + let textures = []; for (let fkey in cube.faces) { - let face = cube.faces[fkey] - if (face.texture === null) continue - let texture = face.getTexture() - textures.push(texture) - normals.push(...cube_face_normals[fkey]) + let face = cube.faces[fkey]; + if (face.texture === null) continue; + let texture = face.getTexture(); + textures.push(texture); + normals.push(...cube_face_normals[fkey]); let uv_outputs = [ [ @@ -1003,45 +1003,45 @@ var codec = new Codec('fbx', { face.uv[0] / Project.getUVWidth(texture), 1 - face.uv[1] / Project.getUVHeight(texture), ], - ] - var rot = face.rotation || 0 + ]; + var rot = face.rotation || 0; while (rot > 0) { - uv_outputs.splice(0, 0, uv_outputs.pop()) - rot -= 90 + uv_outputs.splice(0, 0, uv_outputs.pop()); + rot -= 90; } uv_outputs.forEach(coord => { - uv.push(...coord) - }) + uv.push(...coord); + }); - let vertices + let vertices; switch (fkey) { case 'north': - vertices = [3, 6, 4, -1 - 1] - break + vertices = [3, 6, 4, -1 - 1]; + break; case 'east': - vertices = [2, 3, 1, -1 - 0] - break + vertices = [2, 3, 1, -1 - 0]; + break; case 'south': - vertices = [7, 2, 0, -1 - 5] - break + vertices = [7, 2, 0, -1 - 5]; + break; case 'west': - vertices = [6, 7, 5, -1 - 4] - break + vertices = [6, 7, 5, -1 - 4]; + break; case 'up': - vertices = [5, 0, 1, -1 - 4] - break + vertices = [5, 0, 1, -1 - 4]; + break; case 'down': - vertices = [6, 3, 2, -1 - 7] - break + vertices = [6, 3, 2, -1 - 7]; + break; } - indices.push(...vertices) + indices.push(...vertices); } - DefinitionCounter.geometry++ + DefinitionCounter.geometry++; - let used_textures = Texture.all.filter(t => textures.includes(t)) + let used_textures = Texture.all.filter(t => textures.includes(t)); - let geo_id = getID(cube.uuid + '_geo') + let geo_id = getID(cube.uuid + '_geo'); let geometry = { _key: 'Geometry', _values: [geo_id, `Geometry::${unique_name}`, 'Mesh'], @@ -1127,13 +1127,13 @@ var codec = new Codec('fbx', { TypedIndex: 0, }, }, - } - Objects['key' + geo_id] = geometry + }; + Objects['key' + geo_id] = geometry; Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], id: [geo_id, getID(cube.uuid)], - }) + }); used_textures.forEach(tex => { Connections.push({ name: [ @@ -1141,30 +1141,30 @@ var codec = new Codec('fbx', { `Model::${unique_name}`, ], id: [getID(tex.uuid + '_m'), getID(cube.uuid)], - }) - }) - }) + }); + }); + }); // Textures Texture.all.forEach(tex => { - DefinitionCounter.material++ - DefinitionCounter.texture++ - DefinitionCounter.image++ + DefinitionCounter.material++; + DefinitionCounter.texture++; + DefinitionCounter.image++; - let fileContent = null - let fileName = tex.path - let relativeName = tex.name + let fileContent = null; + let fileName = tex.path; + let relativeName = tex.name; // If no file path, use embedded texture if (tex.path == '') { - fileName = '' - relativeName = '' + fileName = ''; + relativeName = ''; } if (options.embed_textures || tex.path == '') { - fileContent = tex.getBase64() + fileContent = tex.getBase64(); } - let unique_name = getUniqueName('texture', tex.uuid, tex.name) + let unique_name = getUniqueName('texture', tex.uuid, tex.name); let mat_object = { _key: 'Material', @@ -1211,7 +1211,7 @@ var codec = new Codec('fbx', { }, P5: { _key: 'P', _values: ['Opacity', 'double', 'Number', '', TNum('D', 1)] }, }, - } + }; let tex_object = { _key: 'Texture', _values: [getID(tex.uuid + '_t'), `Texture::${unique_name}`, ''], @@ -1225,7 +1225,7 @@ var codec = new Codec('fbx', { ModelUVScaling: [TNum('D', 1), TNum('D', 1)], Texture_Alpha_Source: 'None', Cropping: [0, 0, 0, 0], - } + }; let image_object = { _key: 'Video', _values: [getID(tex.uuid + '_i'), `Video::${unique_name}`, 'Clip'], @@ -1237,34 +1237,34 @@ var codec = new Codec('fbx', { Filename: fileName, RelativeFilename: relativeName, Content: fileContent, - } - Objects['key' + tex.uuid + '_m'] = mat_object - Objects['key' + tex.uuid + '_t'] = tex_object - Objects['key' + tex.uuid + '_i'] = image_object + }; + Objects['key' + tex.uuid + '_m'] = mat_object; + Objects['key' + tex.uuid + '_t'] = tex_object; + Objects['key' + tex.uuid + '_i'] = image_object; Connections.push({ name: [`Texture::${unique_name}`, `Material::${unique_name}`], id: [getID(tex.uuid + '_t'), getID(tex.uuid + '_m')], property: 'DiffuseColor', - }) + }); Connections.push({ name: [`Video::${unique_name}`, `Texture::${unique_name}`], id: [getID(tex.uuid + '_i'), getID(tex.uuid + '_t')], - }) - }) + }); + }); // Animations if (options.include_animations) { - let anim_clips = Codecs.gltf.buildAnimationTracks(export_scale, false) // Handles sampling of math based curves etc. - let time_factor = 46186158000 // Arbitrary factor, found in three.js FBX importer + let anim_clips = Codecs.gltf.buildAnimationTracks(export_scale, false); // Handles sampling of math based curves etc. + let time_factor = 46186158000; // Arbitrary factor, found in three.js FBX importer anim_clips.forEach(clip => { - DefinitionCounter.animation_stack++ - DefinitionCounter.animation_layer++ + DefinitionCounter.animation_stack++; + DefinitionCounter.animation_layer++; - let stack_id = getID(clip.uuid + '_s') - let layer_id = getID(clip.uuid + '_l') - let unique_name = getUniqueName('animation', clip.uuid, clip.name) - let fbx_duration = Math.round(clip.duration * time_factor) + let stack_id = getID(clip.uuid + '_s'); + let layer_id = getID(clip.uuid + '_l'); + let unique_name = getUniqueName('animation', clip.uuid, clip.name); + let fbx_duration = Math.round(clip.duration * time_factor); let stack = { _key: 'AnimationStack', @@ -1285,24 +1285,24 @@ var codec = new Codec('fbx', { ], }, }, - } + }; let layer = { _key: 'AnimationLayer', _values: [layer_id, `AnimLayer::${unique_name}`, ''], _force_compound: true, - } - Objects['key' + clip.uuid + '_s'] = stack - Objects['key' + clip.uuid + '_l'] = layer + }; + Objects['key' + clip.uuid + '_s'] = stack; + Objects['key' + clip.uuid + '_l'] = layer; Connections.push({ name: [`AnimLayer::${unique_name}`, `AnimStack::${unique_name}`], id: [layer_id, stack_id], - }) + }); clip.tracks.forEach(track => { // Track = CurveNode - DefinitionCounter.animation_curve_node++ - let track_id = getID(clip.uuid + '.' + track.name) - let track_name = `AnimCurveNode::${unique_name}.${track.channel[0].toUpperCase()}` + DefinitionCounter.animation_curve_node++; + let track_id = getID(clip.uuid + '.' + track.name); + let track_name = `AnimCurveNode::${unique_name}.${track.channel[0].toUpperCase()}`; let curve_node = { _key: 'AnimationCurveNode', _values: [track_id, track_name, ''], @@ -1311,9 +1311,9 @@ var codec = new Codec('fbx', { p2: { _key: 'P', _values: [`d|Y`, 'Number', '', 'A', TNum('D', 1)] }, p3: { _key: 'P', _values: [`d|Z`, 'Number', '', 'A', TNum('D', 1)] }, }, - } - let timecodes = track.times.map(second => Math.round(second * time_factor)) - Objects['key' + clip.uuid + '.' + track.name] = curve_node + }; + let timecodes = track.times.map(second => Math.round(second * time_factor)); + Objects['key' + clip.uuid + '.' + track.name] = curve_node; // Connect to bone Connections.push({ @@ -1328,22 +1328,21 @@ var codec = new Codec('fbx', { : track.channel == 'rotation' ? 'Lcl Rotation' : 'Lcl Scaling', - }) + }); // Connect to layer Connections.push({ name: [track_name, `AnimLayer::${unique_name}`], id: [track_id, layer_id], - }) - - ;['X', 'Y', 'Z'].forEach((axis_letter, axis_number) => { - DefinitionCounter.animation_curve++ + }); + ['X', 'Y', 'Z'].forEach((axis_letter, axis_number) => { + DefinitionCounter.animation_curve++; - let curve_id = getID(clip.uuid + '.' + track.name + '.' + axis_letter) - let curve_name = `AnimCurve::${unique_name}.${track.channel[0].toUpperCase()}${axis_letter}` + let curve_id = getID(clip.uuid + '.' + track.name + '.' + axis_letter); + let curve_name = `AnimCurve::${unique_name}.${track.channel[0].toUpperCase()}${axis_letter}`; - let values = track.values.filter((val, i) => i % 3 == axis_number) + let values = track.values.filter((val, i) => i % 3 == axis_number); if (track.channel == 'rotation') { - values.forEach((v, i) => (values[i] = Math.radToDeg(v))) + values.forEach((v, i) => (values[i] = Math.radToDeg(v))); } let curve = { _key: 'AnimationCurve', @@ -1375,17 +1374,17 @@ var codec = new Codec('fbx', { _type: 'i', a: [timecodes.length], }, - } - Objects['key' + clip.uuid + '.' + track.name + axis_letter] = curve + }; + Objects['key' + clip.uuid + '.' + track.name + axis_letter] = curve; // Connect to track Connections.push({ name: [curve_name, track_name], id: [curve_id, track_id], property: `d|${axis_letter}`, - }) - }) - }) + }); + }); + }); Takes[clip.uuid] = { _key: 'Take', @@ -1393,15 +1392,15 @@ var codec = new Codec('fbx', { FileName: `${unique_name}.tak`, LocalTime: [0, fbx_duration], ReferenceTime: [0, fbx_duration], - } - }) + }; + }); } // Object definitions - model.push(formatFBXComment('Object definitions')) - let total_definition_count = 1 + model.push(formatFBXComment('Object definitions')); + let total_definition_count = 1; for (let key in DefinitionCounter) { - total_definition_count += DefinitionCounter[key] + total_definition_count += DefinitionCounter[key]; } model.push({ Definitions: { @@ -2584,84 +2583,84 @@ var codec = new Codec('fbx', { } : undefined, }, - }) + }); - model.push(formatFBXComment('Object properties')) + model.push(formatFBXComment('Object properties')); model.push({ Objects, - }) + }); // Object connections - model.push(formatFBXComment('Object connections')) - let connections = {} + model.push(formatFBXComment('Object connections')); + let connections = {}; Connections.forEach((connection, i) => { - connections[`connection_${i}_comment`] = { _comment: connection.name.join(', ') } + connections[`connection_${i}_comment`] = { _comment: connection.name.join(', ') }; connections[`connection_${i}`] = { _key: 'C', _values: [connection.property ? 'OP' : 'OO', ...connection.id], - } + }; if (connection.property) { - connections[`connection_${i}`]._values.push(connection.property) + connections[`connection_${i}`]._values.push(connection.property); } //model += `\t;${connection.name.join(', ')}\n`; //model += `\tC: "${property ? 'OP' : 'OO'}",${connection.id.join(',')}${property}\n\n`; - }) + }); model.push({ Connections: connections, - }) + }); // Takes (Animation) - model.push(formatFBXComment('Takes section')) + model.push(formatFBXComment('Takes section')); model.push({ Takes, - }) + }); - scope.dispatchEvent('compile', { model, options }) + scope.dispatchEvent('compile', { model, options }); - let compiled_model + let compiled_model; if (options.encoding == 'binary') { - let top_level_object = {} + let top_level_object = {}; model.forEach(section => { if (typeof section == 'object') { for (let key in section) { - top_level_object[key] = section[key] + top_level_object[key] = section[key]; } } - }) - compiled_model = compileBinaryFBXModel(top_level_object) + }); + compiled_model = compileBinaryFBXModel(top_level_object); } else { compiled_model = model .map(section => { if (typeof section == 'object') { - return compileASCIIFBXSection(section) + return compileASCIIFBXSection(section); } else { - return section + return section; } }) - .join('') + .join(''); } - return compiled_model + return compiled_model; }, write(content, path) { - var scope = this + var scope = this; - Blockbench.writeFile(path, { content }, path => scope.afterSave(path)) + Blockbench.writeFile(path, { content }, path => scope.afterSave(path)); Texture.all.forEach(tex => { - if (tex.error) return - var name = tex.name + if (tex.error) return; + var name = tex.name; if (name.substr(-4).toLowerCase() !== '.png') { - name += '.png' + name += '.png'; } - var image_path = path.split(osfs) - image_path.splice(-1, 1, name) + var image_path = path.split(osfs); + image_path.splice(-1, 1, name); Blockbench.writeFile(image_path.join(osfs), { content: tex.source, savetype: 'image', - }) - }) + }); + }); }, export_options: { encoding: { @@ -2683,10 +2682,10 @@ var codec = new Codec('fbx', { }, async export() { if (Object.keys(this.export_options).length) { - let result = await this.promptExportOptions() - if (result === null) return + let result = await this.promptExportOptions(); + if (result === null) return; } - var scope = this + var scope = this; if (isApp) { Filesystem.exportFile( { @@ -2699,23 +2698,23 @@ var codec = new Codec('fbx', { custom_writer: (a, b) => scope.write(a, b), }, path => this.afterDownload(path) - ) + ); } else { - var archive = new JSZip() - var content = this.compile() + var archive = new JSZip(); + var content = this.compile(); - archive.file((Project.name || 'model') + '.fbx', content) + archive.file((Project.name || 'model') + '.fbx', content); Texture.all.forEach(tex => { - if (tex.error) return - var name = tex.name + if (tex.error) return; + var name = tex.name; if (name.substr(-4).toLowerCase() !== '.png') { - name += '.png' + name += '.png'; } archive.file(name, tex.source.replace('data:image/png;base64,', ''), { base64: true, - }) - }) + }); + }); archive.generateAsync({ type: 'blob' }).then(content => { Filesystem.exportFile( { @@ -2726,11 +2725,11 @@ var codec = new Codec('fbx', { savetype: 'zip', }, path => scope.afterDownload(path) - ) - }) + ); + }); } }, -}) +}); BARS.defineActions(function () { codec.export_action = new Action('export_fbx', { @@ -2738,157 +2737,157 @@ BARS.defineActions(function () { category: 'file', condition: () => Project, click: function () { - codec.export() + codec.export(); }, - }) -}) + }); +}); class BinaryWriter { - array: Uint8Array - buffer: ArrayBuffer - view: DataView - cursor: number - little_endian: boolean - textEncoder: TextEncoder + array: Uint8Array; + buffer: ArrayBuffer; + view: DataView; + cursor: number; + little_endian: boolean; + textEncoder: TextEncoder; constructor(minimal_length, little_endian) { - this.array = new Uint8Array(minimal_length) + this.array = new Uint8Array(minimal_length); // @ts-ignore - this.buffer = this.array.buffer - this.view = new DataView(this.buffer) - this.cursor = 0 - this.little_endian = !!little_endian - this.textEncoder = new TextEncoder() + this.buffer = this.array.buffer; + this.view = new DataView(this.buffer); + this.cursor = 0; + this.little_endian = !!little_endian; + this.textEncoder = new TextEncoder(); } expand(n) { if (this.cursor + n > this.buffer.byteLength) { - var oldArray = this.array + var oldArray = this.array; // Expand by at least 160 bytes at a time to improve performance. Only works for FBX since 176+ arbitrary bytes are added to the file end. - this.array = new Uint8Array(this.cursor + Math.max(n, 176)) - this.buffer = this.array.buffer - this.array.set(oldArray) - this.view = new DataView(this.buffer) + this.array = new Uint8Array(this.cursor + Math.max(n, 176)); + this.buffer = this.array.buffer; + this.array.set(oldArray); + this.view = new DataView(this.buffer); } } WriteUInt8(value) { - this.expand(1) - this.view.setUint8(this.cursor, value) - this.cursor += 1 + this.expand(1); + this.view.setUint8(this.cursor, value); + this.cursor += 1; } WriteUInt16(value) { - this.expand(2) - this.view.setUint16(this.cursor, value, this.little_endian) - this.cursor += 2 + this.expand(2); + this.view.setUint16(this.cursor, value, this.little_endian); + this.cursor += 2; } WriteInt16(value) { - this.expand(2) - this.view.setInt16(this.cursor, value, this.little_endian) - this.cursor += 2 + this.expand(2); + this.view.setInt16(this.cursor, value, this.little_endian); + this.cursor += 2; } WriteInt32(value) { - this.expand(4) - this.view.setInt32(this.cursor, value, this.little_endian) - this.cursor += 4 + this.expand(4); + this.view.setInt32(this.cursor, value, this.little_endian); + this.cursor += 4; } WriteInt64(value) { - this.expand(8) - this.view.setBigInt64(this.cursor, BigInt(value), this.little_endian) - this.cursor += 8 + this.expand(8); + this.view.setBigInt64(this.cursor, BigInt(value), this.little_endian); + this.cursor += 8; } WriteUInt32(value) { - this.expand(4) - this.view.setUint32(this.cursor, value, this.little_endian) - this.cursor += 4 + this.expand(4); + this.view.setUint32(this.cursor, value, this.little_endian); + this.cursor += 4; } WriteFloat32(value) { - this.expand(4) - this.view.setFloat32(this.cursor, value, this.little_endian) - this.cursor += 4 + this.expand(4); + this.view.setFloat32(this.cursor, value, this.little_endian); + this.cursor += 4; } WriteFloat64(value) { - this.expand(8) - this.view.setFloat64(this.cursor, value, this.little_endian) - this.cursor += 8 + this.expand(8); + this.view.setFloat64(this.cursor, value, this.little_endian); + this.cursor += 8; } WriteBoolean(value: boolean) { - this.WriteUInt8(value ? 1 : 0) + this.WriteUInt8(value ? 1 : 0); } Write7BitEncodedInt(value) { while (value >= 0x80) { - this.WriteUInt8(value | 0x80) - value = value >> 7 + this.WriteUInt8(value | 0x80); + value = value >> 7; } - this.WriteUInt8(value) + this.WriteUInt8(value); } WriteRawString(string: string) { - var array = this.EncodeString(string) - this.WriteBytes(array) + var array = this.EncodeString(string); + this.WriteBytes(array); } WriteString(string: string, raw?: boolean) { - var array = this.EncodeString(string) - if (!raw) this.Write7BitEncodedInt(array.byteLength) - this.WriteBytes(array) + var array = this.EncodeString(string); + if (!raw) this.Write7BitEncodedInt(array.byteLength); + this.WriteBytes(array); } WriteU32String(string: string) { - var array = this.EncodeString(string) - this.WriteUInt32(array.byteLength) - this.WriteBytes(array) + var array = this.EncodeString(string); + this.WriteUInt32(array.byteLength); + this.WriteBytes(array); } WriteU32Base64(base64) { - let data = patchedAtob(base64) - let array = Uint8Array.from(data, c => c.charCodeAt(0)) - this.WriteUInt32(array.length) - this.WriteBytes(array) + let data = patchedAtob(base64); + let array = Uint8Array.from(data, c => c.charCodeAt(0)); + this.WriteUInt32(array.length); + this.WriteBytes(array); } WritePoint(point) { - this.expand(8) - this.view.setInt32(this.cursor, point.x, this.little_endian) - this.cursor += 4 - this.view.setInt32(this.cursor, point.y, this.little_endian) - this.cursor += 4 + this.expand(8); + this.view.setInt32(this.cursor, point.x, this.little_endian); + this.cursor += 4; + this.view.setInt32(this.cursor, point.y, this.little_endian); + this.cursor += 4; } WriteVector2(vector) { - this.expand(8) - this.view.setFloat32(this.cursor, vector.x, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, vector.y, this.little_endian) - this.cursor += 4 + this.expand(8); + this.view.setFloat32(this.cursor, vector.x, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, vector.y, this.little_endian); + this.cursor += 4; } WriteVector3(vector) { - this.expand(12) - this.view.setFloat32(this.cursor, vector.x, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, vector.y, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, vector.z, this.little_endian) - this.cursor += 4 + this.expand(12); + this.view.setFloat32(this.cursor, vector.x, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, vector.y, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, vector.z, this.little_endian); + this.cursor += 4; } WriteIntVector3(vector) { - this.expand(12) - this.view.setInt32(this.cursor, vector.x, this.little_endian) - this.cursor += 4 - this.view.setInt32(this.cursor, vector.y, this.little_endian) - this.cursor += 4 - this.view.setInt32(this.cursor, vector.z, this.little_endian) - this.cursor += 4 + this.expand(12); + this.view.setInt32(this.cursor, vector.x, this.little_endian); + this.cursor += 4; + this.view.setInt32(this.cursor, vector.y, this.little_endian); + this.cursor += 4; + this.view.setInt32(this.cursor, vector.z, this.little_endian); + this.cursor += 4; } WriteQuaternion(quat) { - this.expand(16) - this.view.setFloat32(this.cursor, quat.w, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, quat.x, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, quat.y, this.little_endian) - this.cursor += 4 - this.view.setFloat32(this.cursor, quat.z, this.little_endian) - this.cursor += 4 + this.expand(16); + this.view.setFloat32(this.cursor, quat.w, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, quat.x, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, quat.y, this.little_endian); + this.cursor += 4; + this.view.setFloat32(this.cursor, quat.z, this.little_endian); + this.cursor += 4; } WriteBytes(array) { - this.expand(array.byteLength) - this.array.set(array, this.cursor) - this.cursor += array.byteLength + this.expand(array.byteLength); + this.array.set(array, this.cursor); + this.cursor += array.byteLength; } EncodeString(string: string) { - return this.textEncoder.encode(string) + return this.textEncoder.encode(string); } } @@ -2896,112 +2895,112 @@ export function compileBinaryFBXModel(top_level_object) { // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ // https://github.com/jskorepa/fbx.js/blob/master/src/lib/index.ts - let _BLOCK_SENTINEL_DATA + let _BLOCK_SENTINEL_DATA; if (_FBX_VERSION < 7500) { - _BLOCK_SENTINEL_DATA = new Uint8Array(Array(13).fill(0x00)) + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(13).fill(0x00)); } else { - _BLOCK_SENTINEL_DATA = new Uint8Array(Array(25).fill(0x00)) + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(25).fill(0x00)); } // Awful exceptions from Blender: those "classes" of elements seem to need block sentinel even when having no children and some props. - const _KEYS_IGNORE_BLOCK_SENTINEL = ['AnimationStack', 'AnimationLayer'] + const _KEYS_IGNORE_BLOCK_SENTINEL = ['AnimationStack', 'AnimationLayer']; // TODO: if FBX_VERSION >= 7500, use 64-bit offsets (for read_fbx_elem_uint) - var writer = new BinaryWriter(20, true) + var writer = new BinaryWriter(20, true); // Header - writer.WriteRawString('Kaydara FBX Binary ') - writer.WriteUInt8(0x00) - writer.WriteUInt8(0x1a) - writer.WriteUInt8(0x00) + writer.WriteRawString('Kaydara FBX Binary '); + writer.WriteUInt8(0x00); + writer.WriteUInt8(0x1a); + writer.WriteUInt8(0x00); // Version - writer.WriteUInt32(_FBX_VERSION) + writer.WriteUInt32(_FBX_VERSION); function writeObjectRecursively(key, object) { - let tuple + let tuple; if (typeof object == 'object' && typeof object.map === 'function') { - tuple = object + tuple = object; } else if (typeof object !== 'object') { - tuple = [object] + tuple = [object]; } else if (object._values) { - tuple = object._values + tuple = object._values; } else { - tuple = [] + tuple = []; } let is_data_array = object.hasOwnProperty('_values') && object.hasOwnProperty('a') && - object._type != undefined + object._type != undefined; // EndOffset, change later - let end_offset_index = writer.cursor - writer.WriteUInt32(0) + let end_offset_index = writer.cursor; + writer.WriteUInt32(0); // NumProperties - writer.WriteUInt32(tuple.length) + writer.WriteUInt32(tuple.length); // PropertyListLen, change later - let property_length_index = writer.cursor - writer.WriteUInt32(0) + let property_length_index = writer.cursor; + writer.WriteUInt32(0); // Name - writer.WriteString(key) + writer.WriteString(key); - let property_start_index = writer.cursor + let property_start_index = writer.cursor; // Data Array if (is_data_array) { - let type = object._type || 'i' - if (!object._type) console.log('default', key, 'to int') - let array = object.a - if (array instanceof Array == false) array = [array] + let type = object._type || 'i'; + if (!object._type) console.log('default', key, 'to int'); + let array = object.a; + if (array instanceof Array == false) array = [array]; - writer.WriteRawString(type) - writer.WriteUInt32(array.length) + writer.WriteRawString(type); + writer.WriteUInt32(array.length); // Encoding (compression, unused by Blockbench) - writer.WriteUInt32(0) + writer.WriteUInt32(0); // Compressed Length (but we don't use compression, so it's just the data length) - let data_size = 0 + let data_size = 0; switch (type) { case 'f': case 'i': - data_size = 4 - break + data_size = 4; + break; case 'd': case 'l': - data_size = 8 - break + data_size = 8; + break; case 'b': - data_size = 1 - break + data_size = 1; + break; } - writer.WriteUInt32(array.length * data_size) + writer.WriteUInt32(array.length * data_size); // Contents for (let v of array) { switch (type) { case 'f': - writer.WriteFloat32(v) - break + writer.WriteFloat32(v); + break; case 'd': - writer.WriteFloat64(v) - break + writer.WriteFloat64(v); + break; case 'l': - writer.WriteInt64(v) - break + writer.WriteInt64(v); + break; case 'i': - writer.WriteInt32(v) - break + writer.WriteInt32(v); + break; case 'b': - writer.WriteBoolean(v) - break + writer.WriteBoolean(v); + break; } } } else { // Tuple tuple.forEach((value, i) => { - let type: string = typeof value + let type: string = typeof value; if (typeof value == 'object' && value.isTNum) { - type = value.type - value = value.value + type = value.type; + value = value.value; } if (type == 'number') { - type = value % 1 ? 'D' : 'I' + type = value % 1 ? 'D' : 'I'; //if (!object._type) console.log('default', key, i, 'to', type, object) } // handle number types @@ -3015,44 +3014,44 @@ export function compileBinaryFBXModel(top_level_object) { // L: int64 if (type == 'boolean') { - writer.WriteRawString('C') - writer.WriteBoolean(value) + writer.WriteRawString('C'); + writer.WriteBoolean(value); } else if (type == 'string' && value.startsWith('iV')) { // base64 - writer.WriteRawString('R') - writer.WriteU32Base64(value) + writer.WriteRawString('R'); + writer.WriteU32Base64(value); } else if (type == 'string') { // Replace '::' with 0x00 0x01 and swap the order // E.g. "Geometry::cube" becomes "cube\x00\x01Geometry" // Will this break any normal text that contains those characters? I hope not. if (value.includes('::')) { - const hex00 = String.fromCharCode(0x00) - const hex01 = String.fromCharCode(0x01) + const hex00 = String.fromCharCode(0x00); + const hex01 = String.fromCharCode(0x01); - const parts = value.split('::') - value = parts[1] + hex00 + hex01 + parts[0] + const parts = value.split('::'); + value = parts[1] + hex00 + hex01 + parts[0]; } // string - writer.WriteRawString('S') - if (value.startsWith('_')) value = value.substring(1) - writer.WriteU32String(value) + writer.WriteRawString('S'); + if (value.startsWith('_')) value = value.substring(1); + writer.WriteU32String(value); } else if (type == 'Y') { - writer.WriteRawString('Y') - writer.WriteInt16(value) + writer.WriteRawString('Y'); + writer.WriteInt16(value); } else if (type == 'I') { - writer.WriteRawString('I') - writer.WriteInt32(value) + writer.WriteRawString('I'); + writer.WriteInt32(value); } else if (type == 'F') { - writer.WriteRawString('F') - writer.WriteFloat32(value) + writer.WriteRawString('F'); + writer.WriteFloat32(value); } else if (type == 'D') { - writer.WriteRawString('D') - writer.WriteFloat64(value) + writer.WriteRawString('D'); + writer.WriteFloat64(value); } else if (type == 'L') { - writer.WriteRawString('L') - writer.WriteInt64(value) + writer.WriteRawString('L'); + writer.WriteInt64(value); } - }) + }); } // Property Byte Length @@ -3060,141 +3059,141 @@ export function compileBinaryFBXModel(top_level_object) { property_length_index, writer.cursor - property_start_index, writer.little_endian - ) + ); // Nested List if (typeof object == 'object' && object instanceof Array == false && !is_data_array) { - let is_nested = false + let is_nested = false; for (let key in object) { - if (typeof key == 'string' && key.startsWith('_')) continue - if (object[key] === undefined) continue - let child = object[key] - if (child === null || child._comment) continue - if (child._key) key = child._key + if (typeof key == 'string' && key.startsWith('_')) continue; + if (object[key] === undefined) continue; + let child = object[key]; + if (child === null || child._comment) continue; + if (child._key) key = child._key; - is_nested = true + is_nested = true; - writeObjectRecursively(key, child) + writeObjectRecursively(key, child); } // Null Record, indicating a nested list. if ( is_nested || (Object.keys(object).length === 0 && !_KEYS_IGNORE_BLOCK_SENTINEL.includes(key)) ) { - writer.WriteBytes(_BLOCK_SENTINEL_DATA) + writer.WriteBytes(_BLOCK_SENTINEL_DATA); } } // End Offset - writer.view.setUint32(end_offset_index, writer.cursor, writer.little_endian) + writer.view.setUint32(end_offset_index, writer.cursor, writer.little_endian); } //writeObjectRecursively('', top_level_object); for (let key in top_level_object) { - writeObjectRecursively(key, top_level_object[key]) + writeObjectRecursively(key, top_level_object[key]); } - writer.WriteBytes(_BLOCK_SENTINEL_DATA) + writer.WriteBytes(_BLOCK_SENTINEL_DATA); // Footer // Write the FOOT ID let footer_id = [ 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e, 0x00, 0x00, 0x00, 0x00, - ] - writer.WriteBytes(new Uint8Array(footer_id)) + ]; + writer.WriteBytes(new Uint8Array(footer_id)); // padding for alignment (values between 1 & 16 observed) // if already aligned to 16, add a full 16 bytes padding. - const offset = writer.cursor - let pad = ((offset + 15) & ~15) - offset - if (pad === 0) pad = 16 + const offset = writer.cursor; + let pad = ((offset + 15) & ~15) - offset; + if (pad === 0) pad = 16; for (let i = 0; i < pad; i++) { - writer.WriteUInt8(0x00) + writer.WriteUInt8(0x00); } // Write the FBX version - writer.WriteUInt32(_FBX_VERSION) + writer.WriteUInt32(_FBX_VERSION); // Write some footer magic - writer.WriteBytes(new Uint8Array(Array(120).fill(0x00))) + writer.WriteBytes(new Uint8Array(Array(120).fill(0x00))); let footer_magic = [ 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b, - ] - writer.WriteBytes(new Uint8Array(footer_magic)) + ]; + writer.WriteBytes(new Uint8Array(footer_magic)); // Cut the array to the cursor location (because the writer expand method can have added extra bytes) - const output = writer.array.subarray(0, writer.cursor) + const output = writer.array.subarray(0, writer.cursor); - return output + return output; } export function compileASCIIFBXSection(object: FBXNode) { - let depth = 0 + let depth = 0; function indent() { - let spaces = '' + let spaces = ''; for (let i = 0; i < depth; i++) { - spaces += '\t' + spaces += '\t'; } - return spaces + return spaces; } function handleValue(value) { - if (typeof value == 'object' && value.isTNum) value = value.value - if (typeof value == 'boolean') return value ? 'Y' : 'N' - if (typeof value == 'string' && value.startsWith('_')) return value.substring(1) - if (typeof value == 'string') return '"' + value + '"' - return value + if (typeof value == 'object' && value.isTNum) value = value.value; + if (typeof value == 'boolean') return value ? 'Y' : 'N'; + if (typeof value == 'string' && value.startsWith('_')) return value.substring(1); + if (typeof value == 'string') return '"' + value + '"'; + return value; } function joinArray(array) { - let string = '' - if (Array.length == 0) return string - string += array[0] + let string = ''; + if (Array.length == 0) return string; + string += array[0]; for (let i = 1; i < array.length; i++) { - let item = array[i] - string += ',' + let item = array[i]; + string += ','; if (typeof item !== 'number') { - string += ' ' + string += ' '; } - string += item + string += item; } - return string + return string; } function handleObjectChildren(parent) { - let output = '' + let output = ''; for (let key in parent) { - if (typeof key == 'string' && key.startsWith('_')) continue - if (parent[key] === undefined || parent[key] === null) continue - let object = parent[key] + if (typeof key == 'string' && key.startsWith('_')) continue; + if (parent[key] === undefined || parent[key] === null) continue; + let object = parent[key]; if (object._comment) { - output += `\n${indent()};${object._comment}\n` - continue + output += `\n${indent()};${object._comment}\n`; + continue; } - if (object._key) key = object._key + if (object._key) key = object._key; - let values = '' + let values = ''; if (typeof object == 'object' && typeof object.map === 'function') { - values = joinArray(object.map(handleValue)) + values = joinArray(object.map(handleValue)); } else if (typeof object !== 'object') { - values = handleValue(object) + values = handleValue(object); } else if (object._values) { - values = joinArray(object._values.map(handleValue)) + values = joinArray(object._values.map(handleValue)); } - output += `${indent()}${key}: ${values}` + output += `${indent()}${key}: ${values}`; - let content + let content; if (typeof object == 'object' && typeof object.map !== 'function') { - depth++ - content = handleObjectChildren(object) - depth-- + depth++; + content = handleObjectChildren(object); + depth--; } if (content || object._force_compound) { - output += ` {\n${content}${indent()}}\n` + output += ` {\n${content}${indent()}}\n`; } else { - output += '\n' + output += '\n'; } } - return output + return output; } - return handleObjectChildren(object) + return handleObjectChildren(object); } diff --git a/js/io/formats/generic.ts b/js/io/formats/generic.ts index 0067bc889..966c7f44c 100644 --- a/js/io/formats/generic.ts +++ b/js/io/formats/generic.ts @@ -34,4 +34,4 @@ new ModelFormat('free', { animated_textures: true, locators: true, pbr: true, -}) +}); diff --git a/js/io/formats/skin.ts b/js/io/formats/skin.ts index 5cf967587..c67b9dc65 100644 --- a/js/io/formats/skin.ts +++ b/js/io/formats/skin.ts @@ -1,30 +1,30 @@ -import { CanvasFrame } from '../../lib/CanvasFrame' -import StateMemory from '../../util/state_memory' -import { Panels, setProjectTitle } from '../../interface/interface' -import { getAllGroups } from '../../outliner/group' -import { DefaultCameraPresets } from '../../preview/preview' -import { MinecraftEULA } from '../../preview/preview_scenes' -import { TextureGenerator } from '../../texturing/texture_generator' -import { Panel } from '../../interface/panels' -import { Blockbench } from '../../api' -import { FormResultValue } from '../../interface/form' +import { CanvasFrame } from '../../lib/CanvasFrame'; +import StateMemory from '../../util/state_memory'; +import { Panels, setProjectTitle } from '../../interface/interface'; +import { getAllGroups } from '../../outliner/group'; +import { DefaultCameraPresets } from '../../preview/preview'; +import { MinecraftEULA } from '../../preview/preview_scenes'; +import { TextureGenerator } from '../../texturing/texture_generator'; +import { Panel } from '../../interface/panels'; +import { Blockbench } from '../../api'; +import { FormResultValue } from '../../interface/form'; type SkinPreset = { - display_name: string - pose?: boolean - model?: string - model_java?: string - model_bedrock?: string + display_name: string; + pose?: boolean; + model?: string; + model_java?: string; + model_bedrock?: string; variants?: { [key: string]: { - name: string - model: string - } - } -} -export const skin_presets: Record = {} + name: string; + model: string; + }; + }; +}; +export const skin_presets: Record = {}; -type SkinPoseData = Record +type SkinPoseData = Record; const DefaultPoses: Record = { none: { Head: [0, 0, 0], @@ -82,75 +82,75 @@ const DefaultPoses: Record = { RightLeg: { rotation: [2.5, 0, 0], offset: [0, 1, -2] }, LeftLeg: [-28, 0, 0], }, -} +}; export const codec = new Codec('skin_model', { name: 'Skin Model', remember: false, compile(options) { - if (options === undefined) options = 0 + if (options === undefined) options = 0; type BoneData = { - name: string - parent?: string - pivot?: ArrayVector3 - rotation?: ArrayVector3 - reset?: boolean - mirror?: boolean - cubes?: CubeData[] - } + name: string; + parent?: string; + pivot?: ArrayVector3; + rotation?: ArrayVector3; + reset?: boolean; + mirror?: boolean; + cubes?: CubeData[]; + }; type CubeData = { - origin: ArrayVector3 - size: ArrayVector3 - inflate?: number - } + origin: ArrayVector3; + size: ArrayVector3; + inflate?: number; + }; let entitymodel = { name: Project.geometry_name.split('.')[0], texturewidth: Project.texture_width, textureheight: Project.texture_height, bones: undefined as undefined | BoneData[], - } - let bones = [] + }; + let bones = []; - let groups = getAllGroups() + let groups = getAllGroups(); groups.forEach(function (g) { - if (g.type !== 'group') return + if (g.type !== 'group') return; //Bone let bone: BoneData = { name: g.name, - } - bone.name = g.name + }; + bone.name = g.name; if (g.parent.type === 'group') { - bone.parent = g.parent.name + bone.parent = g.parent.name; } - bone.pivot = g.origin.slice() - bone.pivot[0] *= -1 + bone.pivot = g.origin.slice(); + bone.pivot[0] *= -1; if (!g.rotation.allEqual(0)) { - bone.rotation = [-g.rotation[0], -g.rotation[1], g.rotation[2]] + bone.rotation = [-g.rotation[0], -g.rotation[1], g.rotation[2]]; } - if (g.reset) bone.reset = true - if (g.mirror_uv) bone.mirror = true + if (g.reset) bone.reset = true; + if (g.mirror_uv) bone.mirror = true; //Elements - let cubes = [] + let cubes = []; for (let obj of g.children) { if (obj.export && obj instanceof Cube) { // @ts-ignore - let template = Codecs.bedrock.compileCube(obj, g) - cubes.push(template) + let template = Codecs.bedrock.compileCube(obj, g); + cubes.push(template); } } if (cubes.length) { - bone.cubes = cubes + bone.cubes = cubes; } - bones.push(bone) - }) + bones.push(bone); + }); if (bones.length) { - entitymodel.bones = bones + entitymodel.bones = bones; } - this.dispatchEvent('compile', { model: entitymodel, options }) - return entitymodel + this.dispatchEvent('compile', { model: entitymodel, options }); + return entitymodel; }, // @ts-ignore parse( @@ -160,73 +160,73 @@ export const codec = new Codec('skin_model', { pose = true, layer_template ) { - this.dispatchEvent('parse', { model: data }) - Project.texture_width = data.texturewidth || 64 - Project.texture_height = data.textureheight || 64 - if (data.texture_resolution_factor) resolution *= data.texture_resolution_factor + this.dispatchEvent('parse', { model: data }); + Project.texture_width = data.texturewidth || 64; + Project.texture_height = data.textureheight || 64; + if (data.texture_resolution_factor) resolution *= data.texture_resolution_factor; - Interface.Panels.skin_pose.inside_vue.pose = Project.skin_pose = pose ? 'natural' : 'none' + Interface.Panels.skin_pose.inside_vue.pose = Project.skin_pose = pose ? 'natural' : 'none'; - let bones = {} - let template_cubes = {} + let bones = {}; + let template_cubes = {}; if (data.bones) { - let included_bones = [] + let included_bones = []; data.bones.forEach(function (b) { - included_bones.push(b.name) - }) + included_bones.push(b.name); + }); data.bones.forEach(function (b, bi) { let group = new Group({ name: b.name, origin: b.pivot, rotation: pose && b.pose ? b.pose : b.rotation, - }).init() - group.isOpen = true - bones[b.name] = group + }).init(); + group.isOpen = true; + bones[b.name] = group; if (b.pivot) { - group.origin[0] *= -1 + group.origin[0] *= -1; } - group.rotation[0] *= -1 - group.rotation[1] *= -1 + group.rotation[0] *= -1; + group.rotation[1] *= -1; - group.mirror_uv = b.mirror === true - group.reset = b.reset === true - group.skin_original_origin = group.origin.slice() as ArrayVector3 + group.mirror_uv = b.mirror === true; + group.reset = b.reset === true; + group.skin_original_origin = group.origin.slice() as ArrayVector3; if (b.cubes) { b.cubes.forEach(function (cube) { - let base_cube = Codecs.bedrock.parseCube(cube, group) - template_cubes[Cube.all.indexOf(base_cube)] = cube - }) + let base_cube = Codecs.bedrock.parseCube(cube, group); + template_cubes[Cube.all.indexOf(base_cube)] = cube; + }); } if (b.children) { b.children.forEach(function (cg) { - cg.addTo(group) - }) + cg.addTo(group); + }); } - let parent_group: 'root' | OutlinerNode = 'root' + let parent_group: 'root' | OutlinerNode = 'root'; if (b.parent) { if (bones[b.parent]) { - parent_group = bones[b.parent] + parent_group = bones[b.parent]; } else { data.bones.forEach(function (ib) { if (ib.name === b.parent) { ib.children && ib.children.length ? ib.children.push(group) - : (ib.children = [group]) + : (ib.children = [group]); } - }) + }); } } - group.addTo(parent_group) - }) + group.addTo(parent_group); + }); } if (!Cube.all.find(cube => cube.box_uv)) { - Project.box_uv = false + Project.box_uv = false; } - let texture: Texture | undefined + let texture: Texture | undefined; if (typeof texture_file == 'object') { - texture = new Texture().fromFile(texture_file).add(false) + texture = new Texture().fromFile(texture_file).add(false); } else if (texture_file != false && resolution) { texture = generateTemplate( Project.texture_width * resolution, @@ -235,45 +235,45 @@ export const codec = new Codec('skin_model', { data.name, data.eyes, layer_template - ) + ); } for (let index in template_cubes) { if (template_cubes[index].visibility === false) { - Cube.all[index].visibility = false + Cube.all[index].visibility = false; } } if (texture) { texture.load_callback = function () { - Modes.options.paint.select() - } + Modes.options.paint.select(); + }; } if (data.camera_angle) { // @ts-ignore - main_preview.loadAnglePreset(DefaultCameraPresets.find(p => p.id == data.camera_angle)) + main_preview.loadAnglePreset(DefaultCameraPresets.find(p => p.id == data.camera_angle)); } - Canvas.updateAllBones() - Canvas.updateVisibility() - setProjectTitle() - updateSelection() + Canvas.updateAllBones(); + Canvas.updateVisibility(); + setProjectTitle(); + updateSelection(); }, -}) -codec.export = null +}); +codec.export = null; codec.rebuild = function (model_id: string, pose?: string) { - let [preset_id, variant] = model_id.split('.') - let preset = skin_presets[preset_id] + let [preset_id, variant] = model_id.split('.'); + let preset = skin_presets[preset_id]; let model_raw = preset.model || (variant == 'java' ? preset.model_java : preset.model_bedrock) || - preset.variants[variant].model - let model = JSON.parse(model_raw) + preset.variants[variant].model; + let model = JSON.parse(model_raw); // @ts-ignore - codec.parse(model, undefined, true, pose && pose !== 'none') + codec.parse(model, undefined, true, pose && pose !== 'none'); if (pose && pose !== 'none' && pose !== 'natural') { setTimeout(() => { - setDefaultPose(pose) - }, 1) + setDefaultPose(pose); + }, 1); } -} +}; export const format = new ModelFormat('skin', { icon: 'icon-player', @@ -301,58 +301,58 @@ export const format = new ModelFormat('skin', { edit_mode: false, pose_mode: true, codec, -}) +}); format.new = function () { - skin_dialog.show() - return true -} -skin_presets + skin_dialog.show(); + return true; +}; +skin_presets; function setDefaultPose(pose_id: string) { - let angles = DefaultPoses[pose_id] - loadPose(angles) - Panels.skin_pose.inside_vue.pose = pose_id - Project.skin_pose = pose_id + let angles = DefaultPoses[pose_id]; + loadPose(angles); + Panels.skin_pose.inside_vue.pose = pose_id; + Project.skin_pose = pose_id; } function loadPose(pose_data: SkinPoseData) { - Panels.skin_pose.inside_vue.pose = '' + Panels.skin_pose.inside_vue.pose = ''; Group.all.forEach(group => { - if (!group.skin_original_origin) return - let offset = group.origin.slice().V3_subtract(group.skin_original_origin) - group.origin.V3_set(group.skin_original_origin) - }) + if (!group.skin_original_origin) return; + let offset = group.origin.slice().V3_subtract(group.skin_original_origin); + group.origin.V3_set(group.skin_original_origin); + }); for (let name in pose_data) { - let group = Group.all.find(g => g.name == name || g.name.replace(/\s/g, '') == name) + let group = Group.all.find(g => g.name == name || g.name.replace(/\s/g, '') == name); if (group) { if (pose_data[name] instanceof Array) { - group.extend({ rotation: pose_data[name] }) + group.extend({ rotation: pose_data[name] }); } else { - group.extend({ rotation: pose_data[name].rotation }) - group.origin.V3_add(pose_data[name].offset) + group.extend({ rotation: pose_data[name].rotation }); + group.origin.V3_add(pose_data[name].offset); } } } Canvas.updateView({ groups: Group.all, group_aspects: { transform: true }, - }) + }); } function getPoseData(): SkinPoseData { - const data: SkinPoseData = {} + const data: SkinPoseData = {}; for (let group of Group.all) { - if (!group.skin_original_origin) continue - let offset = group.origin.slice().V3_subtract(group.skin_original_origin) - let rotation = group.rotation.slice() as ArrayVector3 + if (!group.skin_original_origin) continue; + let offset = group.origin.slice().V3_subtract(group.skin_original_origin); + let rotation = group.rotation.slice() as ArrayVector3; if (offset.allEqual(0) == false) { data[group.name] = { offset, rotation, - } + }; } else if (rotation.allEqual(0) == false) { - data[group.name] = rotation + data[group.name] = rotation; } } - return data + return data; } export function generateTemplate( @@ -366,16 +366,16 @@ export function generateTemplate( let texture = new Texture({ internal: true, name: name + '.png', - }) + }); - let canvas = document.createElement('canvas') - let ctx = canvas.getContext('2d') - canvas.width = width - canvas.height = height + let canvas = document.createElement('canvas'); + let ctx = canvas.getContext('2d'); + canvas.width = width; + canvas.height = height; if (Project.box_uv) { Cube.all.forEach((cube, i) => { - let template_cube = cubes[i] + let template_cube = cubes[i]; if (layer_template || !template_cube.layer) { TextureGenerator.paintCubeBoxTemplate( cube, @@ -383,34 +383,34 @@ export function generateTemplate( canvas, null, template_cube.layer - ) + ); } - }) + }); } else if (cubes[0] && !cubes[0].layer) { - ctx.fillStyle = TextureGenerator.face_data.up.c1 - ctx.fillRect(0, 0, width, height) - ctx.fillStyle = TextureGenerator.face_data.up.c2 - ctx.fillRect(1, 1, width - 2, height - 2) + ctx.fillStyle = TextureGenerator.face_data.up.c1; + ctx.fillRect(0, 0, width, height); + ctx.fillStyle = TextureGenerator.face_data.up.c2; + ctx.fillRect(1, 1, width - 2, height - 2); } if (eyes) { - let res_multiple = canvas.width / Project.texture_width - ctx.fillStyle = '#cdefff' + let res_multiple = canvas.width / Project.texture_width; + ctx.fillStyle = '#cdefff'; eyes.forEach(eye => { ctx.fillRect( eye[0] * res_multiple, eye[1] * res_multiple, (eye[2] || 2) * res_multiple, (eye[3] || 2) * res_multiple - ) - }) + ); + }); } - let dataUrl = canvas.toDataURL() - texture.fromDataURL(dataUrl).add(false) - return texture + let dataUrl = canvas.toDataURL(); + texture.fromDataURL(dataUrl).add(false); + return texture; } -export const model_options: Record = {} -let selected_model = '' +export const model_options: Record = {}; +let selected_model = ''; export const skin_dialog = new Dialog({ title: tl('dialog.skin.title'), id: 'skin', @@ -429,17 +429,17 @@ export const skin_dialog = new Dialog({ bedrock_edition: 'Bedrock Edition', }, condition(form: Record) { - return skin_presets[form.model as string].model_bedrock + return skin_presets[form.model as string].model_bedrock; }, }, variant: { label: 'dialog.skin.variant', type: 'select', options() { - return (selected_model && skin_presets[selected_model].variants) || {} + return (selected_model && skin_presets[selected_model].variants) || {}; }, condition(form) { - return skin_presets[form.model].variants + return skin_presets[form.model].variants; }, }, resolution: { @@ -489,14 +489,14 @@ export const skin_dialog = new Dialog({ layer_template: { type: 'checkbox', label: 'dialog.skin.layer_template', value: false }, }, onFormChange(result) { - selected_model = result.model as string - let variants = skin_presets[result.model as string].variants + selected_model = result.model as string; + let variants = skin_presets[result.model as string].variants; if (variants) { for (let key in variants) { if (!result.variant || !variants[result.variant as string]) { - result.variant = key - skin_dialog.setFormValues({ variant: key }, false) - break + result.variant = key; + skin_dialog.setFormValues({ variant: key }, false); + break; } } } @@ -504,90 +504,90 @@ export const skin_dialog = new Dialog({ onConfirm: function (result) { if (result.model == 'flat_texture') { if (result.texture) { - Codecs.image.load(result.texture) + Codecs.image.load(result.texture); } else { - Formats.image.new() + Formats.image.new(); } } else { if (newProject(format)) { - let preset = skin_presets[result.model] - let raw_model: string + let preset = skin_presets[result.model]; + let raw_model: string; if (preset.model_bedrock) { raw_model = result.game_edition == 'java_edition' ? preset.model_java - : preset.model_bedrock + : preset.model_bedrock; } else if (preset.variants) { - raw_model = preset.variants[result.variant].model + raw_model = preset.variants[result.variant].model; } else { - raw_model = preset.model + raw_model = preset.model; } - let model = JSON.parse(raw_model) - let resolution = result.resolution + let model = JSON.parse(raw_model); + let resolution = result.resolution; if (resolution == 1) { - resolution = model.default_resolution ?? 16 + resolution = model.default_resolution ?? 16; } - let texture: string | undefined | false + let texture: string | undefined | false; if (result.texture_source == 'upload_texture') { - texture = result.texture_file + texture = result.texture_file; } else if (result.texture_source == 'load_texture') { if (!model.external_textures) { Blockbench.showQuickMessage( 'This skin model does not support loading textures from Minecraft at the moment', 3000 - ) + ); } else if (!navigator.onLine) { Blockbench.showQuickMessage( 'Failed to load skin texture from Minecraft. Check your internet connection.', 3000 - ) + ); } else { - texture = false + texture = false; } } // @ts-ignore - codec.parse(model, resolution / 16, texture, result.pose, result.layer_template) - Project.skin_model = result.model + codec.parse(model, resolution / 16, texture, result.pose, result.layer_template); + Project.skin_model = result.model; if (preset.model_bedrock) { Project.skin_model += - '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock') + '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock'); } else if (preset.variants) { - Project.skin_model += '.' + result.variant + Project.skin_model += '.' + result.variant; } if (result.texture_source == 'load_texture' && navigator.onLine) { MinecraftEULA.promptUser('skin').then(async function (accepted) { - if (accepted != true) return - if (!model.external_textures) return + if (accepted != true) return; + if (!model.external_textures) return; for (let path of model.external_textures) { - let frame = new CanvasFrame() - let resource_path = `https://github.com/Mojang/bedrock-samples/blob/main/resource_pack/textures/${path}?raw=true` + let frame = new CanvasFrame(); + let resource_path = `https://github.com/Mojang/bedrock-samples/blob/main/resource_pack/textures/${path}?raw=true`; frame.loadFromURL(resource_path).then(() => { - let dataUrl = (frame.canvas as HTMLCanvasElement).toDataURL() + let dataUrl = (frame.canvas as HTMLCanvasElement).toDataURL(); let texture = new Texture({ internal: true, name: pathToName(path, true), - }) - texture.fromDataURL(dataUrl).add(false) - }) + }); + texture.fromDataURL(dataUrl).add(false); + }); } - }) + }); } } } }, onCancel() { - Blockbench.Format = 0 - Settings.updateSettingsInProfiles() + Blockbench.Format = 0; + Settings.updateSettingsInProfiles(); }, -}) +}); // @ts-ignore -format.setup_dialog = skin_dialog +format.setup_dialog = skin_dialog; type SkinPose = { - name: string - data: SkinPoseData -} -StateMemory.init('skin_poses', 'array') + name: string; + data: SkinPoseData; +}; +StateMemory.init('skin_poses', 'array'); BARS.defineActions(function () { new Mode('pose', { @@ -595,35 +595,35 @@ BARS.defineActions(function () { default_tool: 'rotate_tool', category: 'navigate', condition: () => Format && Format.pose_mode, - }) + }); new Action('toggle_skin_layer', { icon: 'layers_clear', category: 'edit', condition: { formats: ['skin'] }, click: function () { - let edited = [] + let edited = []; Cube.all.forEach(cube => { if (cube.name.toLowerCase().includes('layer')) { - edited.push(cube) + edited.push(cube); } - }) - if (!edited.length) return - Undo.initEdit({ elements: edited }) - let value = !edited[0].visibility + }); + if (!edited.length) return; + Undo.initEdit({ elements: edited }); + let value = !edited[0].visibility; edited.forEach(cube => { - cube.visibility = value - }) - Undo.finishEdit('Toggle skin layer') - Canvas.updateVisibility() + cube.visibility = value; + }); + Undo.finishEdit('Toggle skin layer'); + Canvas.updateVisibility(); }, - }) + }); new Action('convert_minecraft_skin_variant', { icon: 'compare_arrows', category: 'edit', condition: { formats: ['skin'], method: () => Group.all.find(g => g.name == 'Right Arm') }, click() { - let is_slim = Cube.all.find(c => c.name.match(/arm/i)).size(0) == 3 + let is_slim = Cube.all.find(c => c.name.match(/arm/i)).size(0) == 3; new Dialog('convert_minecraft_skin_variant', { title: 'action.convert_minecraft_skin_variant', form: { @@ -646,65 +646,67 @@ BARS.defineActions(function () { let right_arm = Group.all .find(g => g.name == 'Right Arm') - ?.children?.filter(el => el instanceof Cube) ?? [] + ?.children?.filter(el => el instanceof Cube) ?? []; let left_arm = Group.all .find(g => g.name == 'Left Arm') - ?.children?.filter(el => el instanceof Cube) ?? [] - let elements = right_arm.concat(left_arm) - Undo.initEdit({ elements }) + ?.children?.filter(el => el instanceof Cube) ?? []; + let elements = right_arm.concat(left_arm); + Undo.initEdit({ elements }); for (let cube of right_arm) { - cube.to[0] = result.model == 'alex' ? 7 : 8 + cube.to[0] = result.model == 'alex' ? 7 : 8; } for (let cube of left_arm) { - cube.from[0] = result.model == 'alex' ? -7 : -8 + cube.from[0] = result.model == 'alex' ? -7 : -8; } Canvas.updateView({ elements: right_arm.concat(left_arm), element_aspects: { geometry: true, uv: true }, selection: true, - }) - Undo.finishEdit('Convert Minecraft skin model') + }); + Undo.finishEdit('Convert Minecraft skin model'); if (result.adjust_texture) { - let textures = Texture.all.filter(tex => tex.selected || tex.multi_selected) - if (!textures.length) textures = [Texture.getDefault()] - if (!textures[0]) return + let textures = Texture.all.filter( + tex => tex.selected || tex.multi_selected + ); + if (!textures.length) textures = [Texture.getDefault()]; + if (!textures[0]) return; const arm_uv_positions: [number, number][] = [ [40, 16], [40, 32], [32, 48], [48, 48], - ] + ]; type Translation = { - area: [number, number, number, number] - offset: [number, number] - } - let translations: Translation[] + area: [number, number, number, number]; + offset: [number, number]; + }; + let translations: Translation[]; if (result.model == 'alex') { translations = [ { area: [6, 0, 10, 16], offset: [-1, 0] }, { area: [9, 0, 2, 4], offset: [-1, 0] }, { area: [13, 4, 2, 12], offset: [-1, 0] }, - ] + ]; } else { translations = [ { area: [5, 0, 10, 16], offset: [1, 0] }, { area: [9, 0, 2, 4], offset: [1, 0] }, { area: [13, 4, 2, 12], offset: [1, 0] }, - ] + ]; } - Undo.initEdit({ textures, bitmap: true }) + Undo.initEdit({ textures, bitmap: true }); for (let texture of textures) { if (texture.layers_enabled) { - texture.layers_enabled = false - texture.selected_layer = null - texture.layers.empty() + texture.layers_enabled = false; + texture.selected_layer = null; + texture.layers.empty(); } texture.edit( () => { - let ctx = texture.ctx + let ctx = texture.ctx; for (let position of arm_uv_positions) { for (let translation of translations) { let data = ctx.getImageData( @@ -712,7 +714,7 @@ BARS.defineActions(function () { position[1] + translation.area[1], translation.area[2], translation.area[3] - ) + ); ctx.putImageData( data, position[0] + @@ -721,33 +723,33 @@ BARS.defineActions(function () { position[1] + translation.area[1] + translation.offset[1] - ) + ); } if (result.model == 'alex') { - ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4) - ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12) + ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4); + ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12); } } }, { no_undo: true } - ) + ); } - UVEditor.vue.layer = null - updateSelection() - Undo.finishEdit('Convert Minecraft skin texture') + UVEditor.vue.layer = null; + updateSelection(); + Undo.finishEdit('Convert Minecraft skin texture'); } }, - }).show() + }).show(); }, - }) + }); new Action('export_minecraft_skin', { icon: 'icon-player', category: 'file', condition: () => Format == format && Texture.all[0], click: function () { - Texture.all[0].save(true) + Texture.all[0].save(true); }, - }) + }); let explode_skin_model = new Toggle('explode_skin_model', { icon: () => 'open_in_full', @@ -755,49 +757,49 @@ BARS.defineActions(function () { condition: { formats: ['skin'] }, default: false, onChange(exploded_view) { - Undo.initEdit({ elements: Cube.all, exploded_view: !exploded_view }) + Undo.initEdit({ elements: Cube.all, exploded_view: !exploded_view }); Cube.all.forEach(cube => { let center = [ cube.from[0] + (cube.to[0] - cube.from[0]) / 2, cube.from[1], cube.from[2] + (cube.to[2] - cube.from[2]) / 2, - ] - let offset = cube.name.toLowerCase().includes('leg') ? 1 : 0.5 - center.V3_multiply(exploded_view ? offset : -offset / (1 + offset)) - cube.from.V3_add(center as ArrayVector3) - cube.to.V3_add(center as ArrayVector3) - }) - Project.exploded_view = exploded_view + ]; + let offset = cube.name.toLowerCase().includes('leg') ? 1 : 0.5; + center.V3_multiply(exploded_view ? offset : -offset / (1 + offset)); + cube.from.V3_add(center as ArrayVector3); + cube.to.V3_add(center as ArrayVector3); + }); + Project.exploded_view = exploded_view; Undo.finishEdit(exploded_view ? 'Explode skin model' : 'Revert exploding skin model', { elements: Cube.all, exploded_view: exploded_view, - }) - Canvas.updateView({ elements: Cube.all, element_aspects: { geometry: true } }) - this.setIcon(this.icon) + }); + Canvas.updateView({ elements: Cube.all, element_aspects: { geometry: true } }); + this.setIcon(this.icon); }, - }) + }); Blockbench.on('select_project', () => { - explode_skin_model.value = !!Project.exploded_view - explode_skin_model.updateEnabledState() - }) + explode_skin_model.value = !!Project.exploded_view; + explode_skin_model.updateEnabledState(); + }); new Action('custom_skin_poses', { icon: 'format_list_bulleted', category: 'view', condition: { formats: ['skin'], modes: ['pose'] }, click(e) { - new Menu(this.children()).open(e.target as HTMLElement) + new Menu(this.children()).open(e.target as HTMLElement); }, children() { - let options = [] - let memory_list = StateMemory.get('skin_poses') as SkinPose[] + let options = []; + let memory_list = StateMemory.get('skin_poses') as SkinPose[]; memory_list.forEach((pose: SkinPose, i: number) => { let option = { name: pose.name, icon: 'accessibility', id: i.toString(), click() { - loadPose(pose.data) + loadPose(pose.data); }, children: [ { @@ -805,27 +807,27 @@ BARS.defineActions(function () { name: 'action.custom_skin_poses.update', description: 'action.custom_skin_poses.update.desc', click() { - pose.data = getPoseData() - StateMemory.save('skin_poses') + pose.data = getPoseData(); + StateMemory.save('skin_poses'); }, }, { icon: 'delete', name: 'generic.delete', click() { - memory_list.remove(pose) - StateMemory.save('skin_poses') + memory_list.remove(pose); + StateMemory.save('skin_poses'); }, }, ], - } - options.push(option) - }) + }; + options.push(option); + }); - options.push('_', 'add_custom_skin_pose') - return options + options.push('_', 'add_custom_skin_pose'); + return options; }, - }) + }); new Action('add_custom_skin_pose', { icon: 'add', category: 'view', @@ -835,14 +837,14 @@ BARS.defineActions(function () { let pose: SkinPose = { name: value, data: getPoseData(), - } - let memory_list = StateMemory.get('skin_poses') as SkinPose[] - memory_list.push(pose) - StateMemory.save('skin_poses') - }) + }; + let memory_list = StateMemory.get('skin_poses') as SkinPose[]; + memory_list.push(pose); + StateMemory.save('skin_poses'); + }); }, - }) -}) + }); +}); // @ts-ignore Interface.definePanels(function () { @@ -864,11 +866,11 @@ Interface.definePanels(function () { data() { return { pose: 'default', - } + }; }, methods: { setPose(pose) { - setDefaultPose(pose) + setDefaultPose(pose); }, }, template: ` @@ -885,8 +887,8 @@ Interface.definePanels(function () { `, }, - }) -}) + }); +}); // Source: https://github.com/Mojang/bedrock-samples/, licensed under the Minecraft EULA // With modifications for usability @@ -974,7 +976,7 @@ skin_presets.steve = { } ] }`, -} +}; skin_presets.alex = { display_name: 'Player - Slim', pose: true, @@ -1138,7 +1140,7 @@ skin_presets.alex = { } ] }`, -} +}; skin_presets.flat_texture = { display_name: 'Texture', @@ -1164,7 +1166,7 @@ skin_presets.flat_texture = { } ] }`, -} +}; skin_presets.block = { display_name: 'Block', model: `{ @@ -1192,7 +1194,7 @@ skin_presets.block = { } ] }`, -} +}; skin_presets.allay = { display_name: 'Allay', @@ -1267,7 +1269,7 @@ skin_presets.allay = { } ] }`, -} +}; skin_presets.armadillo = { display_name: 'Armadillo', model: `{ @@ -1358,7 +1360,7 @@ skin_presets.armadillo = { } ] }`, -} +}; skin_presets.armor_main = { display_name: 'Armor (Main)', pose: true, @@ -1424,7 +1426,7 @@ skin_presets.armor_main = { } ] }`, -} +}; skin_presets.armor_leggings = { display_name: 'Armor (Leggings)', pose: true, @@ -1462,7 +1464,7 @@ skin_presets.armor_leggings = { } ] }`, -} +}; skin_presets.armor_stand = { display_name: 'Armor Stand', model: `{ @@ -1538,7 +1540,7 @@ skin_presets.armor_stand = { } ] }`, -} +}; skin_presets.axolotl = { display_name: 'Axolotl', model: `{ @@ -1643,7 +1645,7 @@ skin_presets.axolotl = { } ] }`, -} +}; skin_presets.bamboo_raft = { display_name: 'Bamboo Raft', model: `{ @@ -1681,7 +1683,7 @@ skin_presets.bamboo_raft = { } ] }`, -} +}; skin_presets.banner = { display_name: 'Banner', model: `{ @@ -1708,7 +1710,7 @@ skin_presets.banner = { } ] }`, -} +}; skin_presets.bat = { display_name: 'Bat', pose: true, @@ -1881,7 +1883,7 @@ skin_presets.bat = { }`, }, }, -} +}; skin_presets.bed = { display_name: 'Bed', model_bedrock: `{ @@ -1962,7 +1964,7 @@ skin_presets.bed = { } ] }`, -} +}; skin_presets.bee = { display_name: 'Bee', model: `{ @@ -2036,7 +2038,7 @@ skin_presets.bee = { } ] }`, -} +}; skin_presets.bell = { display_name: 'Bell', model: `{ @@ -2055,7 +2057,7 @@ skin_presets.bell = { } ] }`, -} +}; skin_presets.blaze = { display_name: 'Blaze', model: `{ @@ -2160,7 +2162,7 @@ skin_presets.blaze = { } ] }`, -} +}; skin_presets.boat = { display_name: 'Boat', model: `{ @@ -2235,7 +2237,7 @@ skin_presets.boat = { } ] }`, -} +}; skin_presets.bogged = { display_name: 'Bogged', model: `{ @@ -2328,7 +2330,7 @@ skin_presets.bogged = { } ] }`, -} +}; skin_presets.bogged_layer = { display_name: 'Bogged/Stray Layer', model: `{ @@ -2395,7 +2397,7 @@ skin_presets.bogged_layer = { } ] }`, -} +}; skin_presets.breeze = { display_name: 'Breeze', model: `{ @@ -2442,7 +2444,7 @@ skin_presets.breeze = { } ] }`, -} +}; skin_presets.breeze_tornado = { display_name: 'Breeze Tornado', model: `{ @@ -2485,7 +2487,7 @@ skin_presets.breeze_tornado = { } ] }`, -} +}; skin_presets.camel = { display_name: 'Camel', model: `{ @@ -2618,7 +2620,7 @@ skin_presets.camel = { } ] }`, -} +}; skin_presets.cat = { display_name: 'Cat', model: `{ @@ -2703,7 +2705,7 @@ skin_presets.cat = { } ] }`, -} +}; skin_presets.cape_elytra = { display_name: 'Cape + Elytra', model: `{ @@ -2743,7 +2745,7 @@ skin_presets.cape_elytra = { } ] }`, -} +}; skin_presets.chest = { display_name: 'Chest', model: `{ @@ -2764,7 +2766,7 @@ skin_presets.chest = { } ] }`, -} +}; skin_presets.chest_left = { display_name: 'Chest Left', model: `{ @@ -2785,7 +2787,7 @@ skin_presets.chest_left = { } ] }`, -} +}; skin_presets.chest_right = { display_name: 'Chest Right', model: `{ @@ -2806,7 +2808,7 @@ skin_presets.chest_right = { } ] }`, -} +}; skin_presets.chicken = { display_name: 'Chicken', variants: { @@ -2952,7 +2954,7 @@ skin_presets.chicken = { }`, }, }, -} +}; skin_presets.cod = { display_name: 'Cod', model: `{ @@ -3012,7 +3014,7 @@ skin_presets.cod = { } ] }`, -} +}; skin_presets.copper_golem = { display_name: 'Copper Golem', model: `{ @@ -3086,7 +3088,7 @@ skin_presets.copper_golem = { } ] }`, -} +}; skin_presets.cow = { display_name: 'Cow', variants: { @@ -3375,7 +3377,7 @@ skin_presets.cow = { }`, }, }, -} +}; skin_presets.creaking = { display_name: 'Creaking', model: `{ @@ -3458,7 +3460,7 @@ skin_presets.creaking = { } ] }`, -} +}; skin_presets.creeper = { display_name: 'Creeper', model: `{ @@ -3520,7 +3522,7 @@ skin_presets.creeper = { } ] }`, -} +}; skin_presets.dolphin = { display_name: 'Dolphin', pose: true, @@ -3676,7 +3678,7 @@ skin_presets.dolphin = { } ] }`, -} +}; skin_presets.enderdragon = { display_name: 'Ender Dragon', pose: true, @@ -4037,7 +4039,7 @@ skin_presets.enderdragon = { } ] }`, -} +}; skin_presets.enderman = { display_name: 'Enderman', model: `{ @@ -4092,7 +4094,7 @@ skin_presets.enderman = { } ] }`, -} +}; skin_presets.endermite = { display_name: 'Endermite', model: `{ @@ -4134,7 +4136,7 @@ skin_presets.endermite = { } ] }`, -} +}; skin_presets.evocation_fang = { display_name: 'Evocation Fang', model: `{ @@ -4170,7 +4172,7 @@ skin_presets.evocation_fang = { } ] }`, -} +}; skin_presets.evoker = { display_name: 'Evoker', model: `{ @@ -4249,7 +4251,7 @@ skin_presets.evoker = { } ] }`, -} +}; skin_presets.fox = { display_name: 'Fox', model_bedrock: `{ @@ -4376,7 +4378,7 @@ skin_presets.fox = { } ] }`, -} +}; skin_presets.frog = { display_name: 'Frog', model: `{ @@ -4486,7 +4488,7 @@ skin_presets.frog = { } ] }`, -} +}; skin_presets.ghast = { display_name: 'Ghast', model: `{ @@ -4581,7 +4583,7 @@ skin_presets.ghast = { } ] }`, -} +}; skin_presets.goat = { display_name: 'Goat', model: `{ @@ -4649,7 +4651,7 @@ skin_presets.goat = { } ] }`, -} +}; skin_presets.guardian = { display_name: 'Guardian', model: `{ @@ -4816,7 +4818,7 @@ skin_presets.guardian = { } ] }`, -} +}; skin_presets.happy_ghast = { display_name: 'Happy Ghast', model: `{ @@ -4912,7 +4914,7 @@ skin_presets.happy_ghast = { } ] }`, -} +}; skin_presets.harness = { display_name: 'Happy Ghast Harness', model: `{ @@ -4939,7 +4941,7 @@ skin_presets.harness = { } ] }`, -} +}; skin_presets.hoglin = { display_name: 'Hoglin', model: `{ @@ -5015,7 +5017,7 @@ skin_presets.hoglin = { } ] }`, -} +}; skin_presets.horse = { display_name: 'Horse', model: `{ @@ -5181,7 +5183,7 @@ skin_presets.horse = { } ] }`, -} +}; skin_presets.irongolem = { display_name: 'Iron Golem', model: `{ @@ -5242,7 +5244,7 @@ skin_presets.irongolem = { } ] }`, -} +}; skin_presets.llama = { display_name: 'Llama', model: `{ @@ -5319,7 +5321,7 @@ skin_presets.llama = { } ] }`, -} +}; skin_presets.lavaslime = { display_name: 'Magma Cube', variants: { @@ -5448,7 +5450,7 @@ skin_presets.lavaslime = { }`, }, }, -} +}; skin_presets.minecart = { display_name: 'Minecart', model: `{ @@ -5503,7 +5505,7 @@ skin_presets.minecart = { } ] }`, -} +}; skin_presets.panda = { display_name: 'Panda', model: `{ @@ -5564,7 +5566,7 @@ skin_presets.panda = { } ] }`, -} +}; skin_presets.parrot = { display_name: 'Parrot', model: `{ @@ -5634,7 +5636,7 @@ skin_presets.parrot = { } ] }`, -} +}; skin_presets.phantom = { display_name: 'Phantom', model: `{ @@ -5720,7 +5722,7 @@ skin_presets.phantom = { } ] }`, -} +}; skin_presets.pig = { display_name: 'Pig', variants: { @@ -5857,7 +5859,7 @@ skin_presets.pig = { }`, }, }, -} +}; skin_presets.piglin = { display_name: 'Piglin', model: `{ @@ -5944,7 +5946,7 @@ skin_presets.piglin = { } ] }`, -} +}; skin_presets.pillager = { display_name: 'Pillager', model: `{ @@ -6013,7 +6015,7 @@ skin_presets.pillager = { } ] }`, -} +}; skin_presets.polarbear = { display_name: 'Polarbear', model: `{ @@ -6072,7 +6074,7 @@ skin_presets.polarbear = { } ] }`, -} +}; skin_presets.pufferfish = { display_name: 'Pufferfish', model: `{ @@ -6335,7 +6337,7 @@ skin_presets.pufferfish = { } ] }`, -} +}; skin_presets.rabbit = { display_name: 'Rabbit', model: `{ @@ -6450,7 +6452,7 @@ skin_presets.rabbit = { } ] }`, -} +}; skin_presets.ravager = { display_name: 'Ravager', model: `{ @@ -6532,7 +6534,7 @@ skin_presets.ravager = { } ] }`, -} +}; skin_presets.salmon = { display_name: 'Salmon', model: `{ @@ -6608,7 +6610,7 @@ skin_presets.salmon = { } ] }`, -} +}; skin_presets.sheep = { display_name: 'Sheep', model: `{ @@ -6667,7 +6669,7 @@ skin_presets.sheep = { } ] }`, -} +}; skin_presets.shield = { display_name: 'Shield', model: `{ @@ -6686,7 +6688,7 @@ skin_presets.shield = { } ] }`, -} +}; skin_presets.shulker = { display_name: 'Shulker', model: `{ @@ -6720,7 +6722,7 @@ skin_presets.shulker = { } ] }`, -} +}; skin_presets.shulker_bullet = { display_name: 'Shulker Bullet', model: `{ @@ -6740,7 +6742,7 @@ skin_presets.shulker_bullet = { } ] }`, -} +}; skin_presets.silverfish = { display_name: 'Silverfish', model: `{ @@ -6830,7 +6832,7 @@ skin_presets.silverfish = { } ] }`, -} +}; skin_presets.skeleton = { display_name: 'Skeleton/Stray', model: `{ @@ -6900,7 +6902,7 @@ skin_presets.skeleton = { } ] }`, -} +}; skin_presets.slime = { display_name: 'Slime', model: `{ @@ -6931,7 +6933,7 @@ skin_presets.slime = { } ] }`, -} +}; skin_presets.sniffer = { display_name: 'Sniffer', model: `{ @@ -7049,7 +7051,7 @@ skin_presets.sniffer = { } ] }`, -} +}; skin_presets.snowgolem = { display_name: 'Snowgolem', model: `{ @@ -7105,7 +7107,7 @@ skin_presets.snowgolem = { } ] }`, -} +}; skin_presets.spider = { display_name: 'Spider', model: `{ @@ -7200,7 +7202,7 @@ skin_presets.spider = { } ] }`, -} +}; skin_presets.spyglass = { display_name: 'Spyglass', model: `{ @@ -7219,7 +7221,7 @@ skin_presets.spyglass = { } ] }`, -} +}; skin_presets.squid = { display_name: 'Squid', model: `{ @@ -7304,7 +7306,7 @@ skin_presets.squid = { } ] }`, -} +}; skin_presets.strider = { display_name: 'Strider', model: `{ @@ -7398,7 +7400,7 @@ skin_presets.strider = { } ] }`, -} +}; skin_presets.tadpole = { display_name: 'Tadpole', model: `{ @@ -7433,7 +7435,7 @@ skin_presets.tadpole = { } ] }`, -} +}; skin_presets.tropicalfish_a = { display_name: 'Tropicalfish A', model: `{ @@ -7478,7 +7480,7 @@ skin_presets.tropicalfish_a = { } ] }`, -} +}; skin_presets.tropicalfish_b = { display_name: 'Tropicalfish B', model: `{ @@ -7524,7 +7526,7 @@ skin_presets.tropicalfish_b = { } ] }`, -} +}; skin_presets.turtle = { display_name: 'Turtle', model_bedrock: `{ @@ -7651,7 +7653,7 @@ skin_presets.turtle = { } ] }`, -} +}; skin_presets.vex = { display_name: 'Vex', model: `{ @@ -7719,7 +7721,7 @@ skin_presets.vex = { } ] }`, -} +}; skin_presets.villager = { display_name: 'Villager (Old)', model: `{ @@ -7777,7 +7779,7 @@ skin_presets.villager = { } ] }`, -} +}; skin_presets.villager_v2 = { display_name: 'Villager (New)', model_java: `{ @@ -7931,7 +7933,7 @@ skin_presets.villager_v2 = { } ] }`, -} +}; skin_presets.vindicator = { display_name: 'Vindicator', model: `{ @@ -8004,7 +8006,7 @@ skin_presets.vindicator = { } ] }`, -} +}; skin_presets.warden = { display_name: 'Warden', model: `{ @@ -8102,7 +8104,7 @@ skin_presets.warden = { } ] }`, -} +}; skin_presets.witch = { display_name: 'Witch', model: `{ @@ -8196,7 +8198,7 @@ skin_presets.witch = { } ] }`, -} +}; skin_presets.witherBoss = { display_name: 'Wither', model: `{ @@ -8258,7 +8260,7 @@ skin_presets.witherBoss = { } ] }`, -} +}; skin_presets.wolf = { display_name: 'Wolf', model: `{ @@ -8331,7 +8333,7 @@ skin_presets.wolf = { } ] }`, -} +}; skin_presets.zombie = { display_name: 'Zombie', pose: true, @@ -8459,7 +8461,7 @@ skin_presets.zombie = { } ] }`, -} +}; skin_presets.zombie_villager_1 = { display_name: 'Zombie Villager (Old)', model: `{ @@ -8520,7 +8522,7 @@ skin_presets.zombie_villager_1 = { } ] }`, -} +}; skin_presets.zombie_villager_2 = { display_name: 'Zombie Villager (New)', model_java: `{ @@ -8675,8 +8677,8 @@ skin_presets.zombie_villager_2 = { } ] }`, -} +}; for (let id in skin_presets) { - model_options[id] = skin_presets[id].display_name + model_options[id] = skin_presets[id].display_name; } diff --git a/js/io/share.ts b/js/io/share.ts index bf3c7738b..3f014b24b 100644 --- a/js/io/share.ts +++ b/js/io/share.ts @@ -1,37 +1,37 @@ -import { Blockbench } from '../api' -import { Dialog } from '../interface/dialog' -import { FormInputType } from '../interface/form' -import { settings } from '../interface/settings' -import { BARS } from '../interface/toolbars' -import { tl } from '../languages' -import { Mesh } from '../outliner/mesh' -import { Outliner } from '../outliner/outliner' -import { ReferenceImage } from '../preview/reference_images' -import { capitalizeFirstLetter } from '../util/util' -import { Codecs } from './codec' +import { Blockbench } from '../api'; +import { Dialog } from '../interface/dialog'; +import { FormInputType } from '../interface/form'; +import { settings } from '../interface/settings'; +import { BARS } from '../interface/toolbars'; +import { tl } from '../languages'; +import { Mesh } from '../outliner/mesh'; +import { Outliner } from '../outliner/outliner'; +import { ReferenceImage } from '../preview/reference_images'; +import { capitalizeFirstLetter } from '../util/util'; +import { Codecs } from './codec'; BARS.defineActions(function () { function uploadSketchfabModel() { if (Outliner.elements.length === 0 || !Format) { - return + return; } - let tag_suggestions = ['low-poly', 'pixel-art', 'NoAI'] - if (Format.id !== 'free') tag_suggestions.push('minecraft') - if (Format.id === 'skin') tag_suggestions.push('skin') - if (!Mesh.all.length) tag_suggestions.push('voxel') + let tag_suggestions = ['low-poly', 'pixel-art', 'NoAI']; + if (Format.id !== 'free') tag_suggestions.push('minecraft'); + if (Format.id === 'skin') tag_suggestions.push('skin'); + if (!Mesh.all.length) tag_suggestions.push('voxel'); let clean_project_name = Project.name .toLowerCase() .replace(/[_.-]+/g, '-') .replace(/[^a-z0-9-]+/, '') - .replace(/-geo/, '') - if (Project.name) tag_suggestions.push(clean_project_name) + .replace(/-geo/, ''); + if (Project.name) tag_suggestions.push(clean_project_name); if (clean_project_name.includes('-')) tag_suggestions.safePush( ...clean_project_name .split('-') .filter(s => s.length > 2 && s != 'geo') .reverse() - ) + ); let categories = { '': '-', @@ -53,7 +53,7 @@ BARS.defineActions(function () { 'science-technology': 'Science & Technology', 'sports-fitness': 'Sports & Fitness', 'weapons-military': 'Weapons & Military', - } + }; var dialog = new Dialog('sketchfab_uploader', { title: 'dialog.sketchfab_uploader.title', @@ -95,11 +95,11 @@ BARS.defineActions(function () { type: 'buttons', buttons: tag_suggestions, click(index) { - let { tags } = dialog.getFormResult() - let new_tag = tag_suggestions[index] + let { tags } = dialog.getFormResult(); + let new_tag = tag_suggestions[index]; if (!(tags as string).split(/\s/g).includes(new_tag)) { - tags += ' ' + new_tag - dialog.setFormValues({ tags }) + tags += ' ' + new_tag; + dialog.setFormValues({ tags }); } }, }, @@ -116,39 +116,39 @@ BARS.defineActions(function () { }, onConfirm(formResult) { if (!formResult.token || !formResult.name) { - Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800) - return + Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800); + return; } if (!(formResult.tags as string).split(' ').includes('blockbench')) { - formResult.tags += ' blockbench' + formResult.tags += ' blockbench'; } - var data = new FormData() - data.append('token', formResult.token as string) - data.append('name', formResult.name as string) - data.append('description', formResult.description as string) - data.append('tags', formResult.tags as string) - data.append('isPublished', (!formResult.draft as boolean).toString()) + var data = new FormData(); + data.append('token', formResult.token as string); + data.append('name', formResult.name as string); + data.append('description', formResult.description as string); + data.append('tags', formResult.tags as string); + data.append('isPublished', (!formResult.draft as boolean).toString()); //data.append('background', JSON.stringify({color: '#00ff00'})) - data.append('private', (formResult.private as boolean).toString()) - data.append('password', formResult.password as string) - data.append('source', 'blockbench') + data.append('private', (formResult.private as boolean).toString()); + data.append('password', formResult.password as string); + data.append('source', 'blockbench'); if (formResult.category1 || formResult.category2) { - let selected_categories: string[] = [] + let selected_categories: string[] = []; if (formResult.category1) - selected_categories.push(formResult.category1 as string) + selected_categories.push(formResult.category1 as string); if (formResult.category2) - selected_categories.push(formResult.category2 as string) - data.append('categories', selected_categories.join(' ')) + selected_categories.push(formResult.category2 as string); + data.append('categories', selected_categories.join(' ')); } - settings.sketchfab_token.set(formResult.token) + settings.sketchfab_token.set(formResult.token); Codecs.gltf.compile({ animations: formResult.animations }).then(content => { - var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }) - var file = new File([blob], 'model.gltf') + var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + var file = new File([blob], 'model.gltf'); - data.append('modelFile', file) + data.append('modelFile', file); $.ajax({ url: 'https://api.sketchfab.com/v3/models', @@ -158,7 +158,7 @@ BARS.defineActions(function () { processData: false, type: 'POST', success: function (response) { - let url = `https://sketchfab.com/models/${response.uid}` + let url = `https://sketchfab.com/models/${response.uid}`; new Dialog('sketchfab_link', { title: tl('message.sketchfab.success'), icon: 'icon-sketchfab', @@ -174,7 +174,7 @@ BARS.defineActions(function () { share_text: true, }, }, - }).show() + }).show(); }, error: function (response) { let response_types = { @@ -187,30 +187,30 @@ BARS.defineActions(function () { [407]: 'Proxy Authentication Required', [408]: 'Request Timeout', [415]: 'Unsupported File Type', - } + }; Blockbench.showQuickMessage( tl('message.sketchfab.error') + `: Error ${response.status} - ${response_types[response.status] || ''}`, 1500 - ) - console.error(response) + ); + console.error(response); }, - }) - }) + }); + }); - dialog.hide() + dialog.hide(); }, - }) - dialog.show() + }); + dialog.show(); } new Action('upload_sketchfab', { icon: 'icon-sketchfab', category: 'file', condition: () => Project && Outliner.elements.length, click() { - uploadSketchfabModel() + uploadSketchfabModel(); }, - }) + }); new Action('share_model', { icon: 'share', @@ -218,14 +218,14 @@ BARS.defineActions(function () { async click() { let thumbnail = await new Promise(resolve => { // @ts-ignore - Preview.selected.screenshot({ width: 640, height: 480 }, resolve) - }) - let image = new Image() - image.src = thumbnail as string - image.width = 320 - image.style.display = 'block' - image.style.margin = 'auto' - image.style.backgroundColor = 'var(--color-back)' + Preview.selected.screenshot({ width: 640, height: 480 }, resolve); + }); + let image = new Image(); + image.src = thumbnail as string; + image.width = 320; + image.style.display = 'block'; + image.style.margin = 'auto'; + image.style.backgroundColor = 'var(--color-back)'; var dialog = new Dialog({ id: 'share_model', @@ -264,19 +264,19 @@ BARS.defineActions(function () { lines: [image], part_order: ['form', 'lines'], onFormChange(form) { - image.style.display = form.thumbnail ? 'block' : 'none' + image.style.display = form.thumbnail ? 'block' : 'none'; }, buttons: ['generic.share', 'dialog.cancel'], onConfirm: function (formResult) { - let name = formResult.name - let expire_time = formResult.expire_time + let name = formResult.name; + let expire_time = formResult.expire_time; let model = Codecs.project.compile({ compressed: false, absolute_paths: false, reference_images: formResult.reference_images, - }) - let data = { name, expire_time, model, thumbnail: undefined } - if (formResult.thumbnail) data.thumbnail = thumbnail + }); + let data = { name, expire_time, model, thumbnail: undefined }; + if (formResult.thumbnail) data.thumbnail = thumbnail; $.ajax({ url: 'https://blckbn.ch/api/model', @@ -286,7 +286,7 @@ BARS.defineActions(function () { dataType: 'json', type: 'POST', success: function (response) { - let link = `https://blckbn.ch/${response.id}` + let link = `https://blckbn.ch/${response.id}`; new Dialog({ id: 'share_model_link', @@ -300,33 +300,33 @@ BARS.defineActions(function () { share_text: true, }, }, - }).show() + }).show(); }, error: function (response) { - let error_text = 'dialog.share_model.failed' + ' - ' + response.status + let error_text = 'dialog.share_model.failed' + ' - ' + response.status; if (response.status == 413) { if ( ReferenceImage.current_project.length && formResult.reference_images ) { - error_text = 'dialog.share_model.too_large_references' + error_text = 'dialog.share_model.too_large_references'; } else { - error_text = 'dialog.share_model.too_large' + error_text = 'dialog.share_model.too_large'; } } Blockbench.showMessageBox({ title: tl('generic.error'), message: error_text, icon: 'error', - }) - console.error(response) + }); + console.error(response); }, - }) + }); - dialog.hide() + dialog.hide(); }, - }) - dialog.show() + }); + dialog.show(); }, - }) -}) + }); +}); diff --git a/js/languages.ts b/js/languages.ts index 3124815bb..58c910e68 100644 --- a/js/languages.ts +++ b/js/languages.ts @@ -1,23 +1,23 @@ -import cz from '../lang/cz.json' -import de from '../lang/de.json' -import en from '../lang/en.json' -import es from '../lang/es.json' -import fr from '../lang/fr.json' -import it from '../lang/it.json' -import ja from '../lang/ja.json' -import ko from '../lang/ko.json' -import nl from '../lang/nl.json' -import pl from '../lang/pl.json' -import pt from '../lang/pt.json' -import ru from '../lang/ru.json' -import sv from '../lang/sv.json' -import tr from '../lang/tr.json' -import uk from '../lang/uk.json' -import vi from '../lang/vi.json' -import zh from '../lang/zh.json' -import zh_tw from '../lang/zh_tw.json' +import cz from '../lang/cz.json'; +import de from '../lang/de.json'; +import en from '../lang/en.json'; +import es from '../lang/es.json'; +import fr from '../lang/fr.json'; +import it from '../lang/it.json'; +import ja from '../lang/ja.json'; +import ko from '../lang/ko.json'; +import nl from '../lang/nl.json'; +import pl from '../lang/pl.json'; +import pt from '../lang/pt.json'; +import ru from '../lang/ru.json'; +import sv from '../lang/sv.json'; +import tr from '../lang/tr.json'; +import uk from '../lang/uk.json'; +import vi from '../lang/vi.json'; +import zh from '../lang/zh.json'; +import zh_tw from '../lang/zh_tw.json'; -type Language = Record +type Language = Record; export const data: Record = { cz: cz, de: de, @@ -37,7 +37,7 @@ export const data: Record = { vi: vi, zh: zh, zh_tw: zh_tw, -} +}; /** * Returns a translated string in the current language @@ -50,33 +50,33 @@ export const tl = function ( variables?: string | number | (string | number)[], default_value?: string ): string { - if (string && string.length > 100) return string - var result = Language.data[string] + if (string && string.length > 100) return string; + var result = Language.data[string]; if (result && result.length > 0) { if (variables) { if (variables instanceof Array == false) { - variables = [variables] + variables = [variables]; } - var i = variables.length + var i = variables.length; while (i > 0) { - i-- - result = result.replace(new RegExp('%' + i, 'g'), variables[i]) + i--; + result = result.replace(new RegExp('%' + i, 'g'), variables[i]); } } - return result + return result; } else if (default_value != undefined) { - return default_value + return default_value; } else { //console.warn('Unable to find translation for key', string); - return string + return string; } -} +}; export const translateUI = function () { $('.tl').each(function (i, obj) { - var text = tl($(obj).text()) - $(obj).text(text) - }) -} + var text = tl($(obj).text()); + $(obj).text(text); + }); +}; export const Language = { /** @@ -118,30 +118,30 @@ export const Language = { language == Language.code || (language == 'en' && Language.data[key] == undefined) ) { - Language.data[key] = strings[key] + Language.data[key] = strings[key]; } } }, toString: () => Language.code, -} +}; // Get language code -let code +let code; try { - code = JSON.parse(localStorage.getItem('settings')).language.value + code = JSON.parse(localStorage.getItem('settings')).language.value; } catch (err) {} if (!code) { - code = navigator.language.replace(/-\w+/, '') + code = navigator.language.replace(/-\w+/, ''); } if (code && Language.options[code]) { - Language.code = code - document.body.parentElement.setAttribute('lang', Language.code) + Language.code = code; + document.body.parentElement.setAttribute('lang', Language.code); } -Language.data = data[Language.code] +Language.data = data[Language.code]; Object.assign(window, { tl, Language, -}) +}); diff --git a/js/lib/CanvasFrame.ts b/js/lib/CanvasFrame.ts index 7b7d1a0fb..0266db63c 100644 --- a/js/lib/CanvasFrame.ts +++ b/js/lib/CanvasFrame.ts @@ -2,127 +2,127 @@ Utility to modify images with a canvas */ export class CanvasFrame { - canvas: HTMLCanvasElement - ctx: CanvasRenderingContext2D + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; - constructor() - constructor(width: number, height: number) + constructor(); + constructor(width: number, height: number); constructor( source?: HTMLCanvasElement | HTMLImageElement | number, copy_canvas?: number | boolean ) { if (source instanceof HTMLCanvasElement) { if (source.getContext('2d') && copy_canvas !== true) { - this.canvas = source + this.canvas = source; } else { - this.createCanvas(source.width, source.height) - this.loadFromImage(source) + this.createCanvas(source.width, source.height); + this.loadFromImage(source); } } else if (source instanceof HTMLImageElement) { - this.createCanvas(source.naturalWidth, source.naturalHeight) - this.loadFromImage(source) + this.createCanvas(source.naturalWidth, source.naturalHeight); + this.loadFromImage(source); } else { - this.createCanvas(source || 16, typeof copy_canvas == 'number' ? copy_canvas : 16) + this.createCanvas(source || 16, typeof copy_canvas == 'number' ? copy_canvas : 16); } - this.ctx = this.canvas.getContext('2d') + this.ctx = this.canvas.getContext('2d'); } get width() { - return this.canvas.width + return this.canvas.width; } get height() { - return this.canvas.height + return this.canvas.height; } createCanvas(width: number, height: number) { - this.canvas = document.createElement('canvas') - this.canvas.width = width - this.canvas.height = height - this.ctx = this.canvas.getContext('2d') + this.canvas = document.createElement('canvas'); + this.canvas.width = width; + this.canvas.height = height; + this.ctx = this.canvas.getContext('2d'); } async loadFromURL(url: string) { - let img = new Image() - img.src = url.replace(/#/g, '%23') + let img = new Image(); + img.src = url.replace(/#/g, '%23'); await new Promise((resolve, reject) => { img.onload = () => { - this.loadFromImage(img) - resolve() - } - img.onerror = reject - }) + this.loadFromImage(img); + resolve(); + }; + img.onerror = reject; + }); } loadFromImage(img: HTMLImageElement | HTMLCanvasElement) { if ('naturalWidth' in img) { - this.canvas.width = img.naturalWidth - this.canvas.height = img.naturalHeight + this.canvas.width = img.naturalWidth; + this.canvas.height = img.naturalHeight; } - this.ctx.drawImage(img, 0, 0) + this.ctx.drawImage(img, 0, 0); } loadFromCanvas(canvas: HTMLCanvasElement) { - this.canvas.width = canvas.width - this.canvas.height = canvas.height - this.ctx.drawImage(canvas, 0, 0) + this.canvas.width = canvas.width; + this.canvas.height = canvas.height; + this.ctx.drawImage(canvas, 0, 0); } autoCrop() { // Based on code by remy, licensed under MIT // https://gist.github.com/remy/784508 - let copy = document.createElement('canvas').getContext('2d') - let pixels = this.ctx.getImageData(0, 0, this.width, this.height) + let copy = document.createElement('canvas').getContext('2d'); + let pixels = this.ctx.getImageData(0, 0, this.width, this.height); let bound = { top: null as null | number, left: null as null | number, right: null as null | number, bottom: null as null | number, - } - let x: number, y: number + }; + let x: number, y: number; for (let i = 0; i < pixels.data.length; i += 4) { if (pixels.data[i + 3] !== 0) { - x = (i / 4) % this.width - y = ~~(i / 4 / this.width) + x = (i / 4) % this.width; + y = ~~(i / 4 / this.width); if (bound.top === null) { - bound.top = y + bound.top = y; } if (bound.left === null) { - bound.left = x + bound.left = x; } else if (x < bound.left) { - bound.left = x + bound.left = x; } if (bound.right === null) { - bound.right = x + bound.right = x; } else if (bound.right < x) { - bound.right = x + bound.right = x; } if (bound.bottom === null) { - bound.bottom = y + bound.bottom = y; } else if (bound.bottom < y) { - bound.bottom = y + bound.bottom = y; } } } let trimHeight = bound.bottom - bound.top + 1, trimWidth = bound.right - bound.left + 1, - trimmed = this.ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight) + trimmed = this.ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight); - copy.canvas.width = trimWidth - copy.canvas.height = trimHeight - copy.putImageData(trimmed, 0, 0) - this.canvas = copy.canvas - this.ctx = copy + copy.canvas.width = trimWidth; + copy.canvas.height = trimHeight; + copy.putImageData(trimmed, 0, 0); + this.canvas = copy.canvas; + this.ctx = copy; } isEmpty(): boolean { - let { data } = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) + let { data } = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); for (let i = 0; i < data.length; i += 4) { - let alpha = data[i + 3] - if (alpha) return false + let alpha = data[i + 3]; + if (alpha) return false; } - return true + return true; } } -Object.assign(window, { CanvasFrame }) +Object.assign(window, { CanvasFrame }); diff --git a/js/lib/libs.ts b/js/lib/libs.ts index 20c187fe2..f698abbb2 100644 --- a/js/lib/libs.ts +++ b/js/lib/libs.ts @@ -1,25 +1,25 @@ -import * as GIFEnc from 'gifenc' -import $ from 'jquery' -import * as threejs from 'three' -import * as FIK from './fik' -import Vue from 'vue/dist/vue.js' -import JSZip from 'jszip' -import Prism from 'prismjs' -import GIF from 'gif.js' -import vSortable from 'vue-sortable' -import Sortable from 'sortablejs' -import { marked } from 'marked' -import { APNGencoder } from './canvas2apng' -import DOMPurify from 'dompurify' +import * as GIFEnc from 'gifenc'; +import $ from 'jquery'; +import * as threejs from 'three'; +import * as FIK from './fik'; +import Vue from 'vue/dist/vue.js'; +import JSZip from 'jszip'; +import Prism from 'prismjs'; +import GIF from 'gif.js'; +import vSortable from 'vue-sortable'; +import Sortable from 'sortablejs'; +import { marked } from 'marked'; +import { APNGencoder } from './canvas2apng'; +import DOMPurify from 'dompurify'; -Vue.use(vSortable) +Vue.use(vSortable); Vue.directive('sortable', { inserted: function (el, binding) { - new Sortable(el, binding.value || {}) + new Sortable(el, binding.value || {}); }, -}) +}); -const THREE = Object.assign({}, threejs) +const THREE = Object.assign({}, threejs); export { GIFEnc, @@ -34,7 +34,7 @@ export { marked, APNGencoder, DOMPurify, -} +}; Object.assign(window, { GIFEnc, GIF, @@ -48,4 +48,4 @@ Object.assign(window, { marked, APNGencoder, DOMPurify, -}) +}); diff --git a/js/main.ts b/js/main.ts index 082b516c4..06d19a333 100644 --- a/js/main.ts +++ b/js/main.ts @@ -1,124 +1,124 @@ //import { createApp } from 'vue' //import App from './App.vue' -import './lib/libs' -import './lib/jquery-ui.min' -import './lib/targa' -import './lib/VuePrismEditor.min' -import './lib/molang-prism-syntax' -import './lib/lzutf8' -import './lib/spectrum.js' -import './lib/color-picker.min' -import './lib/GLTFExporter' -import './lib/CanvasFrame' -import './lib/canvas2apng' -import './lib/easing' -import './native_apis' -import './preview/OrbitControls' +import './lib/libs'; +import './lib/jquery-ui.min'; +import './lib/targa'; +import './lib/VuePrismEditor.min'; +import './lib/molang-prism-syntax'; +import './lib/lzutf8'; +import './lib/spectrum.js'; +import './lib/color-picker.min'; +import './lib/GLTFExporter'; +import './lib/CanvasFrame'; +import './lib/canvas2apng'; +import './lib/easing'; +import './native_apis'; +import './preview/OrbitControls'; -import './languages' -import './util/util' -import './util/json' -import './util/three_custom' -import './util/math_util' -import './util/array_util' -import './util/event_system' -import './util/property' -import './interface/menu' -import './interface/actions' -import './interface/shared_actions' -import './interface/keyboard' -import './misc' -import './api' -import './modes' -import './file_system' -import './interface/vue_components' -import './interface/panels' -import './interface/interface' -import './interface/menu_bar' -import './interface/start_screen' -import './interface/form' -import './interface/dialog' -import './interface/keybinding' -import './interface/settings' -import './interface/about' -import './interface/action_control' -import './copy_paste' -import './undo' +import './languages'; +import './util/util'; +import './util/json'; +import './util/three_custom'; +import './util/math_util'; +import './util/array_util'; +import './util/event_system'; +import './util/property'; +import './interface/menu'; +import './interface/actions'; +import './interface/shared_actions'; +import './interface/keyboard'; +import './misc'; +import './api'; +import './modes'; +import './file_system'; +import './interface/vue_components'; +import './interface/panels'; +import './interface/interface'; +import './interface/menu_bar'; +import './interface/start_screen'; +import './interface/form'; +import './interface/dialog'; +import './interface/keybinding'; +import './interface/settings'; +import './interface/about'; +import './interface/action_control'; +import './copy_paste'; +import './undo'; -import './desktop.js' +import './desktop.js'; -import './interface/setup_settings' -import './interface/settings_window' -import './edit_sessions' -import './validator' -import './outliner/outliner' -import './outliner/element_panel' -import './outliner/collections' -import './outliner/group' -import './outliner/mesh' -import './outliner/cube' -import './outliner/billboard' -import './outliner/texture_mesh' -import './outliner/armature' -import './outliner/armature_bone' -import './outliner/locator' -import './outliner/null_object' -import './outliner/spline_mesh' -import './preview/preview' -import './preview/reference_images' -import './preview/screenshot' -import './preview/canvas' -import './interface/themes' -import './modeling/edit' -import './modeling/transform_gizmo' -import './modeling/transform' -import './modeling/scale' -import './modeling/mesh_editing' -import './modeling/mirror_modeling' -import './modeling/spline_editing' -import './modeling/weight_paint' -import './texturing/textures' -import './texturing/layers' -import './texturing/texture_groups' -import './texturing/texture_flipbook' -import './texturing/uv' -import './texturing/painter' -import './texturing/texture_generator' -import './texturing/edit_image' -import './display_mode/display_mode' -import './display_mode/attachable_preview' -import './animations/animation_mode' -import './animations/animation' -import './animations/molang' -import './animations/timeline_animators' -import './animations/keyframe' -import './animations/timeline' -import './animations/animation_controllers' -import './preview/preview_scenes' -import './predicate_editor' -import './plugin_loader' -import './io/codec' -import './io/format' -import './io/project' -import './io/io' -import './io/share' -import './texturing/color' -import './io/formats/generic' -import './io/formats/bbmodel' -import './io/formats/java_block' -import './io/formats/bedrock' -import './io/formats/bedrock_old' -import './io/formats/obj' -import './io/formats/gltf' -import './io/formats/fbx' -import './io/formats/collada' -import './io/formats/stl' -import './io/formats/modded_entity' -import './io/formats/optifine_jem' -import './io/formats/optifine_jpm' -import './io/formats/skin' -import './io/formats/image' -import './boot_loader' -import './globals' -import './global_types' +import './interface/setup_settings'; +import './interface/settings_window'; +import './edit_sessions'; +import './validator'; +import './outliner/outliner'; +import './outliner/element_panel'; +import './outliner/collections'; +import './outliner/group'; +import './outliner/mesh'; +import './outliner/cube'; +import './outliner/billboard'; +import './outliner/texture_mesh'; +import './outliner/armature'; +import './outliner/armature_bone'; +import './outliner/locator'; +import './outliner/null_object'; +import './outliner/spline_mesh'; +import './preview/preview'; +import './preview/reference_images'; +import './preview/screenshot'; +import './preview/canvas'; +import './interface/themes'; +import './modeling/edit'; +import './modeling/transform_gizmo'; +import './modeling/transform'; +import './modeling/scale'; +import './modeling/mesh_editing'; +import './modeling/mirror_modeling'; +import './modeling/spline_editing'; +import './modeling/weight_paint'; +import './texturing/textures'; +import './texturing/layers'; +import './texturing/texture_groups'; +import './texturing/texture_flipbook'; +import './texturing/uv'; +import './texturing/painter'; +import './texturing/texture_generator'; +import './texturing/edit_image'; +import './display_mode/display_mode'; +import './display_mode/attachable_preview'; +import './animations/animation_mode'; +import './animations/animation'; +import './animations/molang'; +import './animations/timeline_animators'; +import './animations/keyframe'; +import './animations/timeline'; +import './animations/animation_controllers'; +import './preview/preview_scenes'; +import './predicate_editor'; +import './plugin_loader'; +import './io/codec'; +import './io/format'; +import './io/project'; +import './io/io'; +import './io/share'; +import './texturing/color'; +import './io/formats/generic'; +import './io/formats/bbmodel'; +import './io/formats/java_block'; +import './io/formats/bedrock'; +import './io/formats/bedrock_old'; +import './io/formats/obj'; +import './io/formats/gltf'; +import './io/formats/fbx'; +import './io/formats/collada'; +import './io/formats/stl'; +import './io/formats/modded_entity'; +import './io/formats/optifine_jem'; +import './io/formats/optifine_jpm'; +import './io/formats/skin'; +import './io/formats/image'; +import './boot_loader'; +import './globals'; +import './global_types'; diff --git a/js/modeling/edit.ts b/js/modeling/edit.ts index ba3edb901..6a474ce8f 100644 --- a/js/modeling/edit.ts +++ b/js/modeling/edit.ts @@ -1,4 +1,4 @@ -import { Mode } from '../modes' +import { Mode } from '../modes'; BARS.defineActions(function () { new Mode('edit', { @@ -10,16 +10,16 @@ BARS.defineActions(function () { Outliner.elements.forEach(cube => { // @ts-ignore if (cube.preview_controller.updatePixelGrid) - cube.preview_controller.updatePixelGrid(cube) - }) + cube.preview_controller.updatePixelGrid(cube); + }); }, onUnselect: () => { - if (Undo) Undo.closeAmendEditMenu() + if (Undo) Undo.closeAmendEditMenu(); Outliner.elements.forEach(cube => { // @ts-ignore if (cube.preview_controller.updatePixelGrid) - cube.preview_controller.updatePixelGrid(cube) - }) + cube.preview_controller.updatePixelGrid(cube); + }); }, - }) -}) + }); +}); diff --git a/js/modeling/mesh/attach_armature.ts b/js/modeling/mesh/attach_armature.ts index 83cb7e8c9..c93983c8c 100644 --- a/js/modeling/mesh/attach_armature.ts +++ b/js/modeling/mesh/attach_armature.ts @@ -1,49 +1,49 @@ -import { Armature } from '../../outliner/armature' -import { ArmatureBone } from '../../outliner/armature_bone' -import { sameMeshEdge } from './util' -import { THREE } from '../../lib/libs' -import { pointInPolygon } from '../../util/util' -import { Blockbench } from '../../api' +import { Armature } from '../../outliner/armature'; +import { ArmatureBone } from '../../outliner/armature_bone'; +import { sameMeshEdge } from './util'; +import { THREE } from '../../lib/libs'; +import { pointInPolygon } from '../../util/util'; +import { Blockbench } from '../../api'; interface BoneInfo { - bone: ArmatureBone - name: string - tail_offset: THREE.Vector3 - start: THREE.Vector3 - end: THREE.Vector3 - line: THREE.Line3 - _distance?: number - _distance_on_line?: number - _amount?: number - _is_inside?: boolean - _weight?: number + bone: ArmatureBone; + name: string; + tail_offset: THREE.Vector3; + start: THREE.Vector3; + end: THREE.Vector3; + line: THREE.Line3; + _distance?: number; + _distance_on_line?: number; + _amount?: number; + _is_inside?: boolean; + _weight?: number; } interface EdgeLoop { - loop: MeshEdge[] - plane: THREE.Plane - plane_quaternion: THREE.Quaternion - polygon: ArrayVector2[] - vkeys: string[] + loop: MeshEdge[]; + plane: THREE.Plane; + plane_quaternion: THREE.Quaternion; + polygon: ArrayVector2[]; + vkeys: string[]; } function calculateWeights(mesh: Mesh, armature: Armature) { - let armature_bones = armature.getAllBones() - Undo.initEdit({ elements: [mesh, ...armature_bones] }) - mesh.preview_controller.updateTransform(mesh) + let armature_bones = armature.getAllBones(); + Undo.initEdit({ elements: [mesh, ...armature_bones] }); + mesh.preview_controller.updateTransform(mesh); if (armature) { - mesh.sortAllFaceVertices() + mesh.sortAllFaceVertices(); let bone_infos: BoneInfo[] = armature_bones.map(bone => { - let tail_offset = new THREE.Vector3() - let tail_bone = bone.children[0] + let tail_offset = new THREE.Vector3(); + let tail_bone = bone.children[0]; if (tail_bone) { - tail_offset.fromArray(tail_bone.position) + tail_offset.fromArray(tail_bone.position); } else { - tail_offset.y = bone.length + tail_offset.y = bone.length; } - let start = bone.getWorldCenter() - let end = bone.mesh.localToWorld(tail_offset) + let start = bone.getWorldCenter(); + let end = bone.mesh.localToWorld(tail_offset); let data: BoneInfo = { bone, name: bone.name, @@ -51,219 +51,219 @@ function calculateWeights(mesh: Mesh, armature: Armature) { start, end, line: new THREE.Line3(start, end), - } - return data - }) + }; + return data; + }); // Analyze geometry - const vertex_edge_loops: Record = {} + const vertex_edge_loops: Record = {}; for (let vkey in mesh.vertices) { - if (!vertex_edge_loops[vkey]) vertex_edge_loops[vkey] = [] - if (vertex_edge_loops[vkey].length >= 4) continue + if (!vertex_edge_loops[vkey]) vertex_edge_loops[vkey] = []; + if (vertex_edge_loops[vkey].length >= 4) continue; getEdgeLoops(mesh, vkey).forEach(loop => { let coplanar_vertices = [ loop[0][0], loop[Math.floor(loop.length * 0.33)][0], loop[Math.floor(loop.length * 0.66)][0], - ] + ]; let coplanar_points = coplanar_vertices.map(vkey => new THREE.Vector3().fromArray(mesh.vertices[vkey]) - ) + ); let plane = new THREE.Plane().setFromCoplanarPoints( coplanar_points[0], coplanar_points[1], coplanar_points[2] - ) + ); let plane_quaternion = new THREE.Quaternion().setFromUnitVectors( plane.normal, new THREE.Vector3(0, 1, 0) - ) + ); - let polygon: ArrayVector2[] = [] - let vkeys: string[] = [] + let polygon: ArrayVector2[] = []; + let vkeys: string[] = []; loop.forEach((edge: MeshEdge) => { - let vkey2 = edge[0] - let point = new THREE.Vector3().fromArray(mesh.vertices[vkey2]) - plane.projectPoint(point, point) - point.applyQuaternion(plane_quaternion) - polygon.push([point.x, point.z]) - vkeys.push(vkey2) - }) + let vkey2 = edge[0]; + let point = new THREE.Vector3().fromArray(mesh.vertices[vkey2]); + plane.projectPoint(point, point); + point.applyQuaternion(plane_quaternion); + polygon.push([point.x, point.z]); + vkeys.push(vkey2); + }); - let edge_loop = { loop, plane_quaternion, vkeys, polygon, plane } + let edge_loop = { loop, plane_quaternion, vkeys, polygon, plane }; for (let vkey2 of vkeys) { if (!vertex_edge_loops[vkey2]?.length) { - vertex_edge_loops[vkey2] = [edge_loop] + vertex_edge_loops[vkey2] = [edge_loop]; } else { let match = vertex_edge_loops[vkey2].find(edge_loop2 => { return ( edge_loop2.vkeys.length == edge_loop.vkeys.length && edge_loop2.vkeys.allAre(vkey3 => edge_loop.vkeys.includes(vkey3)) - ) - }) - if (!match) vertex_edge_loops[vkey2].push(edge_loop) + ); + }); + if (!match) vertex_edge_loops[vkey2].push(edge_loop); } } - }) + }); } // Calculate base vertex weights - const vertex_main_bone: Record = {} + const vertex_main_bone: Record = {}; for (let vkey in mesh.vertices) { - let global_pos = new THREE.Vector3().fromArray(mesh.vertices[vkey]) - let edge_loops = vertex_edge_loops[vkey] + let global_pos = new THREE.Vector3().fromArray(mesh.vertices[vkey]); + let edge_loops = vertex_edge_loops[vkey]; - let shortest_edge_loop = edge_loops.findHighest(loop => -loop.vkeys.length) + let shortest_edge_loop = edge_loops.findHighest(loop => -loop.vkeys.length); for (let bone_info of bone_infos) { bone_info._is_inside = - shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false + shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false; let closest_point = bone_info.line.closestPointToPoint( global_pos, true, new THREE.Vector3() - ) - bone_info._distance = closest_point.distanceTo(global_pos) - bone_info.line.closestPointToPoint(global_pos, false, closest_point) - bone_info._distance_on_line = closest_point.distanceTo(global_pos) + ); + bone_info._distance = closest_point.distanceTo(global_pos); + bone_info.line.closestPointToPoint(global_pos, false, closest_point); + bone_info._distance_on_line = closest_point.distanceTo(global_pos); } - let inside_bones = bone_infos.filter(bone_infos => bone_infos._is_inside) + let inside_bones = bone_infos.filter(bone_infos => bone_infos._is_inside); let bone_matches = inside_bones.filter( bone_info => bone_info._distance < bone_info._distance_on_line * 1.2 - ) + ); if (!bone_matches.length) { bone_matches = inside_bones.filter( bone_info => bone_info._distance < bone_info._distance_on_line * 2 - ) + ); } let full_match_bones = bone_matches.filter( bone_info => bone_info._distance < bone_info._distance_on_line * 2 - ) + ); if (full_match_bones.length) { - let closest_bone = full_match_bones.findHighest(bone => -bone._distance) - vertex_main_bone[vkey] = closest_bone + let closest_bone = full_match_bones.findHighest(bone => -bone._distance); + vertex_main_bone[vkey] = closest_bone; - closest_bone.bone.vertex_weights[vkey] = 1 + closest_bone.bone.vertex_weights[vkey] = 1; } else { - bone_matches.sort((a, b) => a._distance - b._distance) - bone_matches = bone_matches.slice(0, 3) - vertex_main_bone[vkey] = bone_matches[0] - let amount_sum = 0 + bone_matches.sort((a, b) => a._distance - b._distance); + bone_matches = bone_matches.slice(0, 3); + vertex_main_bone[vkey] = bone_matches[0]; + let amount_sum = 0; for (let match of bone_matches) { match._amount = Math.min( Math.max(match._distance_on_line, 0.04) / match._distance, 1 - ) - amount_sum += match._amount + ); + amount_sum += match._amount; } for (let match of bone_matches) { - match.bone.vertex_weights[vkey] = match._amount / amount_sum + match.bone.vertex_weights[vkey] = match._amount / amount_sum; } } } // Add smoothing for (let vkey in mesh.vertices) { - let closest_vertices = [] + let closest_vertices = []; for (let loop of vertex_edge_loops[vkey]) { - let index = loop.vkeys.indexOf(vkey) - closest_vertices.safePush(loop.vkeys.atWrapped(index + 1)) - closest_vertices.safePush(loop.vkeys.atWrapped(index - 1)) + let index = loop.vkeys.indexOf(vkey); + closest_vertices.safePush(loop.vkeys.atWrapped(index + 1)); + closest_vertices.safePush(loop.vkeys.atWrapped(index - 1)); } if (!vertex_main_bone[vkey]) { - let bones = [] + let bones = []; for (let vkey2 of closest_vertices) { - let bone = vertex_main_bone[vkey2] + let bone = vertex_main_bone[vkey2]; if (bone) { - bones.safePush(bone) - bone._weight = 0 + bones.safePush(bone); + bone._weight = 0; } } if (bones.length == 1) { - bones[0].bone.vertex_weights[vkey] = 1 - vertex_main_bone[vkey] = bones[0] - continue + bones[0].bone.vertex_weights[vkey] = 1; + vertex_main_bone[vkey] = bones[0]; + continue; } // Share between bones - let vertex_position = new THREE.Vector3().fromArray(mesh.vertices[vkey]) - let weight_sum = 0 + let vertex_position = new THREE.Vector3().fromArray(mesh.vertices[vkey]); + let weight_sum = 0; let weighted_vertices = closest_vertices.map(vkey2 => { let distance = Reusable.vec1 .fromArray(mesh.vertices[vkey2]) - .distanceTo(vertex_position) - weight_sum += 1 / distance + .distanceTo(vertex_position); + weight_sum += 1 / distance; return { distance, bone: vertex_main_bone[vkey2], vkey: vkey2, weight: 1 / distance, - } - }) + }; + }); for (let weighted of weighted_vertices) { - if (!weighted.bone) continue - weighted.bone._weight += weighted.weight + if (!weighted.bone) continue; + weighted.bone._weight += weighted.weight; } for (let bone of bones) { - bone.bone.vertex_weights[vkey] = 1 + bone.bone.vertex_weights[vkey] = 1; } } } } - Undo.finishEdit('Attach armature to mesh') - Canvas.updateView({ elements: Mesh.selected, element_aspects: { geometry: true } }) + Undo.finishEdit('Attach armature to mesh'); + Canvas.updateView({ elements: Mesh.selected, element_aspects: { geometry: true } }); } function isBoneInsideLoops(edge_loops: EdgeLoop[], bone_info: BoneInfo): THREE.Vector3 | false { for (let loop of edge_loops) { - let projected_point = loop.plane.intersectLine(bone_info.line, new THREE.Vector3()) - if (!projected_point) continue - projected_point.applyQuaternion(loop.plane_quaternion) - let point = [projected_point.x, projected_point.z] + let projected_point = loop.plane.intersectLine(bone_info.line, new THREE.Vector3()); + if (!projected_point) continue; + projected_point.applyQuaternion(loop.plane_quaternion); + let point = [projected_point.x, projected_point.z]; if (pointInPolygon(point, loop.polygon)) { - return projected_point + return projected_point; } } - return false + return false; } function getEdgeLoops(mesh: Mesh, start_vkey: string) { - let vertices: string[] = [] - let edges: MeshEdge[] = [] + let vertices: string[] = []; + let edges: MeshEdge[] = []; - let processed_faces = [] + let processed_faces = []; function checkFace(face: MeshFace, side_vertices: MeshEdge) { - processed_faces.push(face) - let sorted_vertices = face.vertices.slice() + processed_faces.push(face); + let sorted_vertices = face.vertices.slice(); let side_index_diff = - sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]) - if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse() + sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]); + if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse(); - let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey)) + let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey)); let opposite_index_diff = sorted_vertices.indexOf(opposite_vertices[0]) - - sorted_vertices.indexOf(opposite_vertices[1]) - if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse() + sorted_vertices.indexOf(opposite_vertices[1]); + if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse(); - vertices.safePush(...side_vertices) - edges.push(side_vertices) + vertices.safePush(...side_vertices); + edges.push(side_vertices); // Find next (and previous) face function doNextFace(index: number) { for (let fkey in mesh.faces) { - let ref_face = mesh.faces[fkey] - if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue + let ref_face = mesh.faces[fkey]; + if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue; - let sorted_vertices = ref_face.vertices.slice() + let sorted_vertices = ref_face.vertices.slice(); let vertices = ref_face.vertices.filter( vkey => vkey == side_vertices[index] || vkey == opposite_vertices[index] - ) + ); if (vertices.length >= 2) { let second_vertex = sorted_vertices.find((vkey, i) => { @@ -272,34 +272,34 @@ function getEdgeLoops(mesh: Mesh, start_vkey: string) { vkey !== opposite_vertices[index] && (sorted_vertices.length == 3 || Math.abs(sorted_vertices.indexOf(side_vertices[index]) - i) !== 2) - ) - }) - checkFace(ref_face, [side_vertices[index], second_vertex]) - break + ); + }); + checkFace(ref_face, [side_vertices[index], second_vertex]); + break; } } } - doNextFace(0) - doNextFace(1) + doNextFace(0); + doNextFace(1); } - let start_edges = [] + let start_edges = []; for (let fkey in mesh.faces) { - let face = mesh.faces[fkey] - if (face.vertices.includes(start_vkey) == false) continue + let face = mesh.faces[fkey]; + if (face.vertices.includes(start_vkey) == false) continue; for (let edge of face.getEdges()) { if (edge.includes(start_vkey) && !start_edges.find(e2 => sameMeshEdge(e2.edge, edge))) { - start_edges.push({ edge, face }) + start_edges.push({ edge, face }); } } } - let loops: MeshEdge[][] = [] + let loops: MeshEdge[][] = []; start_edges.forEach(({ edge, face }) => { - edges = [] - checkFace(face, edge) - if (edges.length > 1) loops.push(edges) - }) - return loops + edges = []; + checkFace(face, edge); + if (edges.length > 1) loops.push(edges); + }); + return loops; } BARS.defineActions(() => { @@ -307,8 +307,8 @@ BARS.defineActions(() => { icon: 'accessibility', condition: () => Mesh.selected[0]?.getArmature(), click(e) { - let mesh = Mesh.selected[0] - calculateWeights(mesh, mesh.getArmature()) + let mesh = Mesh.selected[0]; + calculateWeights(mesh, mesh.getArmature()); }, - }) -}) + }); +}); diff --git a/js/modeling/mesh/proportional_edit.ts b/js/modeling/mesh/proportional_edit.ts index 82940f946..771f0516b 100644 --- a/js/modeling/mesh/proportional_edit.ts +++ b/js/modeling/mesh/proportional_edit.ts @@ -7,106 +7,106 @@ export const ProportionalEdit = { selection: '', }, calculateWeights(mesh: Mesh) { - if (!pe_toggle.value) return + if (!pe_toggle.value) return; - let selected_vertices = mesh.getSelectedVertices() - let { range, falloff, selection } = ProportionalEdit.config - let linear_distance = selection == 'linear' + let selected_vertices = mesh.getSelectedVertices(); + let { range, falloff, selection } = ProportionalEdit.config; + let linear_distance = selection == 'linear'; - let all_mesh_connections + let all_mesh_connections; if (!linear_distance) { - all_mesh_connections = {} + all_mesh_connections = {}; for (let fkey in mesh.faces) { - let face = mesh.faces[fkey] + let face = mesh.faces[fkey]; face.getEdges().forEach(edge => { if (!all_mesh_connections[edge[0]]) { - all_mesh_connections[edge[0]] = [edge[1]] + all_mesh_connections[edge[0]] = [edge[1]]; } else { - all_mesh_connections[edge[0]].safePush(edge[1]) + all_mesh_connections[edge[0]].safePush(edge[1]); } if (!all_mesh_connections[edge[1]]) { - all_mesh_connections[edge[1]] = [edge[0]] + all_mesh_connections[edge[1]] = [edge[0]]; } else { - all_mesh_connections[edge[1]].safePush(edge[0]) + all_mesh_connections[edge[1]].safePush(edge[0]); } - }) + }); } } - ProportionalEdit.vertex_weights[mesh.uuid] = {} + ProportionalEdit.vertex_weights[mesh.uuid] = {}; for (let vkey in mesh.vertices) { - if (selected_vertices.includes(vkey)) continue + if (selected_vertices.includes(vkey)) continue; - let distance = Infinity + let distance = Infinity; if (linear_distance) { // Linear Distance selected_vertices.forEach(vkey2 => { - let pos1 = mesh.vertices[vkey] - let pos2 = mesh.vertices[vkey2] + let pos1 = mesh.vertices[vkey]; + let pos2 = mesh.vertices[vkey2]; let distance_square = Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + - Math.pow(pos1[2] - pos2[2], 2) + Math.pow(pos1[2] - pos2[2], 2); if (distance_square < distance) { - distance = distance_square + distance = distance_square; } - }) - distance = Math.sqrt(distance) + }); + distance = Math.sqrt(distance); } else { // Connection Distance - let found_match_depth = 0 - let scanned = [] - let frontier = [vkey] + let found_match_depth = 0; + let scanned = []; + let frontier = [vkey]; depth_crawler: for (let depth = 1; depth <= range; depth++) { - let new_frontier = [] + let new_frontier = []; for (let vkey1 of frontier) { let connections = all_mesh_connections[vkey1]?.filter( vkey2 => !scanned.includes(vkey2) - ) - if (!connections || connections.length == 0) continue - scanned.push(...connections) - new_frontier.push(...connections) + ); + if (!connections || connections.length == 0) continue; + scanned.push(...connections); + new_frontier.push(...connections); } for (let vkey2 of new_frontier) { if (selected_vertices.includes(vkey2)) { - found_match_depth = depth - break depth_crawler + found_match_depth = depth; + break depth_crawler; } } - frontier = new_frontier + frontier = new_frontier; } if (found_match_depth) { - distance = found_match_depth + distance = found_match_depth; } } - if (distance > range) continue + if (distance > range) continue; - let blend = 1 - distance / (linear_distance ? range : range + 1) + let blend = 1 - distance / (linear_distance ? range : range + 1); switch (falloff) { case 'hermite_spline': - blend = Math.hermiteBlend(blend) - break + blend = Math.hermiteBlend(blend); + break; case 'constant': - blend = 1 - break + blend = 1; + break; } - ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend + ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend; } }, editVertices(mesh, per_vertex) { - if (!pe_toggle.value) return + if (!pe_toggle.value) return; - let selected_vertices = mesh.getSelectedVertices() + let selected_vertices = mesh.getSelectedVertices(); for (let vkey in mesh.vertices) { - if (selected_vertices.includes(vkey)) continue + if (selected_vertices.includes(vkey)) continue; - let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey] - per_vertex(vkey, blend) + let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey]; + per_vertex(vkey, blend); } }, -} +}; const pe_toggle = new Toggle('proportional_editing', { icon: 'wifi_tethering', @@ -140,29 +140,29 @@ const pe_toggle = new Toggle('proportional_editing', { }, }, onOpen() { - this.setFormValues({ enabled: pe_toggle.value }) + this.setFormValues({ enabled: pe_toggle.value }); }, onFormChange(formResult) { if (pe_toggle.value != formResult.enabled) { - pe_toggle.trigger() + pe_toggle.trigger(); } - ;(BarItems.proportional_editing_range as NumSlider).update() + (BarItems.proportional_editing_range as NumSlider).update(); }, }), -}) +}); // @ts-ignore -ProportionalEdit.config = (pe_toggle.tool_config as ToolConfig).options +ProportionalEdit.config = (pe_toggle.tool_config as ToolConfig).options; new NumSlider('proportional_editing_range', { category: 'edit', condition: { modes: ['edit'], features: ['meshes'] }, get() { - return ProportionalEdit.config.range + return ProportionalEdit.config.range; }, change(modify) { - ProportionalEdit.config.range = modify(ProportionalEdit.config.range) + ProportionalEdit.config.range = modify(ProportionalEdit.config.range); }, onAfter() { - pe_toggle.tool_config.save() + pe_toggle.tool_config.save(); }, -}) +}); diff --git a/js/modeling/mesh/set_vertex_weights.ts b/js/modeling/mesh/set_vertex_weights.ts index a7f851d71..01b89756a 100644 --- a/js/modeling/mesh/set_vertex_weights.ts +++ b/js/modeling/mesh/set_vertex_weights.ts @@ -1,21 +1,21 @@ -import { Armature } from '../../outliner/armature' -import { ArmatureBone } from '../../outliner/armature_bone' +import { Armature } from '../../outliner/armature'; +import { ArmatureBone } from '../../outliner/armature_bone'; new Action('set_vertex_weights', { icon: 'weight', condition: { modes: ['edit'], method: () => Mesh.selected[0]?.getArmature() }, click() { - let mesh = Mesh.selected[0] - let selected_vertices = mesh.getSelectedVertices() - let armature = mesh.getArmature() as Armature - let available_bones: ArmatureBone[] = armature.getAllBones() - let bone_options = {} + let mesh = Mesh.selected[0]; + let selected_vertices = mesh.getSelectedVertices(); + let armature = mesh.getArmature() as Armature; + let available_bones: ArmatureBone[] = armature.getAllBones(); + let bone_options = {}; for (let bone of available_bones) { - bone_options[bone.uuid] = bone.name + bone_options[bone.uuid] = bone.name; } let affected_bones = available_bones.filter(bone => { - return selected_vertices.find(vkey => bone.vertex_weights[vkey]) - }) + return selected_vertices.find(vkey => bone.vertex_weights[vkey]); + }); // Todo: translations. Add way to configure multiple bones new Dialog('set_vertex_weights', { @@ -30,22 +30,22 @@ new Action('set_vertex_weights', { weight: { type: 'number', label: 'Weight', value: 1, min: 0, max: 1 }, }, onConfirm(result) { - let target_bone = available_bones.find(b => b.uuid == result.bone) - affected_bones.safePush(target_bone) + let target_bone = available_bones.find(b => b.uuid == result.bone); + affected_bones.safePush(target_bone); // @ts-ignore Should be fixed once converting armature_bone.js to ts - Undo.initEdit({ elements: affected_bones }) + Undo.initEdit({ elements: affected_bones }); for (let bone of affected_bones) { - if (bone.uuid == result.bone) continue + if (bone.uuid == result.bone) continue; for (let vkey of selected_vertices) { - delete bone.vertex_weights[vkey] + delete bone.vertex_weights[vkey]; } } for (let vkey of selected_vertices) { - target_bone.vertex_weights[vkey] = result.weight + target_bone.vertex_weights[vkey] = result.weight; } - Undo.finishEdit('Set vertex weights') - updateSelection() + Undo.finishEdit('Set vertex weights'); + updateSelection(); }, - }).show() + }).show(); }, -}) +}); diff --git a/js/modeling/mesh/util.ts b/js/modeling/mesh/util.ts index 8be54f0e7..f33db2c7e 100644 --- a/js/modeling/mesh/util.ts +++ b/js/modeling/mesh/util.ts @@ -1,3 +1,3 @@ export function sameMeshEdge(edge_a: MeshEdge, edge_b: MeshEdge): boolean { - return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]) + return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]); } diff --git a/js/modeling/mirror_modeling.ts b/js/modeling/mirror_modeling.ts index 75f4d2cd1..15041bcbe 100644 --- a/js/modeling/mirror_modeling.ts +++ b/js/modeling/mirror_modeling.ts @@ -1,115 +1,115 @@ -import { Blockbench } from '../api' -import { ArmatureBone } from '../outliner/armature_bone' -import { Billboard } from '../outliner/billboard' -import { flipNameOnAxis } from './transform' +import { Blockbench } from '../api'; +import { ArmatureBone } from '../outliner/armature_bone'; +import { Billboard } from '../outliner/billboard'; +import { flipNameOnAxis } from './transform'; export const MirrorModeling = { initial_transformer_position: 0, outliner_snapshot: null, isCentered(element: OutlinerElement) { - let center = Format.centered_grid ? 0 : 8 - let element_type_options = MirrorModeling.element_types[element.type] + let center = Format.centered_grid ? 0 : 8; + let element_type_options = MirrorModeling.element_types[element.type]; if (element_type_options.isCentered) { - let result = element_type_options.isCentered(element, { center }) - if (result == false) return false + let result = element_type_options.isCentered(element, { center }); + if (result == false) return false; } if (element_type_options.check_parent_symmetry != false) { let isAsymmetrical = parent => { if (parent instanceof OutlinerNode) { - if ('origin' in parent && parent.origin[0] != center) return true + if ('origin' in parent && parent.origin[0] != center) return true; if ('rotation' in parent && (parent.rotation[1] || parent.rotation[2])) - return true - return isAsymmetrical(parent.parent) + return true; + return isAsymmetrical(parent.parent); } - } - if (isAsymmetrical(element.parent)) return false + }; + if (isAsymmetrical(element.parent)) return false; } - return true + return true; }, createClone(original: OutlinerElement, undo_aspects: UndoAspects) { // Create or update clone - let options = (BarItems.mirror_modeling as Toggle).tool_config.options - let mirror_uv = options.mirror_uv - let center = Format.centered_grid ? 0 : 8 - let mirror_element = MirrorModeling.cached_elements[original.uuid]?.counterpart - let element_type_options = MirrorModeling.element_types[original.type] - let element_before_snapshot + let options = (BarItems.mirror_modeling as Toggle).tool_config.options; + let mirror_uv = options.mirror_uv; + let center = Format.centered_grid ? 0 : 8; + let mirror_element = MirrorModeling.cached_elements[original.uuid]?.counterpart; + let element_type_options = MirrorModeling.element_types[original.type]; + let element_before_snapshot; - if (mirror_element == original) return + if (mirror_element == original) return; if (mirror_element) { - element_before_snapshot = mirror_element.getUndoCopy(undo_aspects) - mirror_element.extend(original) + element_before_snapshot = mirror_element.getUndoCopy(undo_aspects); + mirror_element.extend(original); - mirror_element.flip(0, center) + mirror_element.flip(0, center); mirror_element.extend({ name: element_before_snapshot.name, - }) + }); if (!mirror_uv && element_type_options.maintainUV) { - element_type_options.maintainUV(mirror_element, element_before_snapshot) + element_type_options.maintainUV(mirror_element, element_before_snapshot); } if (element_type_options.updateCounterpart) { element_type_options.updateCounterpart(original, mirror_element, { element_before_snapshot, center, - }) + }); } // Update hierarchy up function updateParent(child, child_b) { - let parent = child.parent - let parent_b = child_b.parent - if (parent == parent_b) return - if (parent.type != parent_b.type) return + let parent = child.parent; + let parent_b = child_b.parent; + if (parent == parent_b) return; + if (parent.type != parent_b.type) return; if ( parent instanceof OutlinerNode == false || parent.getTypeBehavior('parent') != true ) - return + return; if ( parent_b instanceof OutlinerNode == false || parent_b.getTypeBehavior('parent') != true ) - return + return; - MirrorModeling.updateParentNodeCounterpart(parent_b, parent) + MirrorModeling.updateParentNodeCounterpart(parent_b, parent); - updateParent(parent, parent_b) + updateParent(parent, parent_b); } - updateParent(original, mirror_element) + updateParent(original, mirror_element); } else { function getParentMirror(child: OutlinerNode) { - let parent = child.parent - if (parent instanceof OutlinerNode == false) return 'root' + let parent = child.parent; + if (parent instanceof OutlinerNode == false) return 'root'; if ( 'origin' in parent && parent.origin[0] == center && MirrorModeling.isParentTreeSymmetrical(child, { center }) ) { - return parent + return parent; } else { let mirror_group_parent = getParentMirror(parent) as OutlinerNode & - OutlinerNodeParentTraits + OutlinerNodeParentTraits; // @ts-ignore - let mirror_group = new parent.constructor(parent) + let mirror_group = new parent.constructor(parent); - flipNameOnAxis(mirror_group, 0, (name: string) => true, parent.name) - mirror_group.origin[0] = MirrorModeling.flipCoord(mirror_group.origin[0]) - mirror_group.rotation[1] *= -1 - mirror_group.rotation[2] *= -1 - mirror_group.isOpen = parent.isOpen + flipNameOnAxis(mirror_group, 0, (name: string) => true, parent.name); + mirror_group.origin[0] = MirrorModeling.flipCoord(mirror_group.origin[0]); + mirror_group.rotation[1] *= -1; + mirror_group.rotation[2] *= -1; + mirror_group.isOpen = parent.isOpen; let parent_list = mirror_group_parent instanceof OutlinerNode ? mirror_group_parent.children - : Outliner.root + : Outliner.root; let match = parent_list.find(node => { - if (node instanceof OutlinerNode == false) return false + if (node instanceof OutlinerNode == false) return false; if ( (node.name == mirror_group.name || Condition(mirror_group.getTypeBehavior('unique_name'))) && @@ -120,88 +120,90 @@ export const MirrorModeling = { node.origin instanceof Array && node.origin.equals(mirror_group.origin) ) { - return true + return true; } - }) + }); if (match) { - return match + return match; } else { - mirror_group.createUniqueName() - mirror_group.addTo(mirror_group_parent).init() - return mirror_group + mirror_group.createUniqueName(); + mirror_group.addTo(mirror_group_parent).init(); + return mirror_group; } } } - let add_to = getParentMirror(original) + let add_to = getParentMirror(original); // @ts-ignore - mirror_element = new original.constructor(original) - mirror_element.addTo(add_to).init() - mirror_element.flip(0, center) + mirror_element = new original.constructor(original); + mirror_element.addTo(add_to).init(); + mirror_element.flip(0, center); if (element_type_options.updateCounterpart) { element_type_options.updateCounterpart(original, mirror_element, { options, center, - }) + }); } } - MirrorModeling.insertElementIntoUndo(mirror_element, undo_aspects, element_before_snapshot) + MirrorModeling.insertElementIntoUndo(mirror_element, undo_aspects, element_before_snapshot); - let { preview_controller } = mirror_element - preview_controller.updateAll(mirror_element) - return mirror_element + let { preview_controller } = mirror_element; + preview_controller.updateAll(mirror_element); + return mirror_element; }, updateParentNodeCounterpart(node: OutlinerNode, original: OutlinerNode) { let keep_properties = { name: node.name, - } - node.extend(original) - node.extend(keep_properties) + }; + node.extend(original); + node.extend(keep_properties); //flipNameOnAxis(node, 0, name => true, original.name); if ('origin' in node) { - node.origin[0] = MirrorModeling.flipCoord(node.origin[0]) + node.origin[0] = MirrorModeling.flipCoord(node.origin[0]); } if ('rotation' in node) { - node.rotation[1] *= -1 - node.rotation[2] *= -1 + node.rotation[1] *= -1; + node.rotation[2] *= -1; } }, getEditSide() { - return Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1 + return ( + Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1 + ); }, flipCoord(input: number): number { if (Format.centered_grid) { - return -input + return -input; } else { - return 16 - input + return 16 - input; } }, getMirrorElement(element: OutlinerElement): OutlinerElement | false { - let element_type_options = MirrorModeling.element_types[element.type] - let center = Format.centered_grid ? 0 : 8 + let element_type_options = MirrorModeling.element_types[element.type]; + let center = Format.centered_grid ? 0 : 8; if (element_type_options.getMirroredElement) { - return element_type_options.getMirroredElement(element, { center }) + return element_type_options.getMirroredElement(element, { center }); } - return false + return false; }, isParentTreeSymmetrical(element: OutlinerNode, { center }) { - if (element.parent instanceof Group && Format.bone_rig == false) return true - let parents = [] - let subject = element - let symmetry_axes = [0] - let off_axes = [1, 2] + if (element.parent instanceof Group && Format.bone_rig == false) return true; + let parents = []; + let subject = element; + let symmetry_axes = [0]; + let off_axes = [1, 2]; while (subject.parent instanceof OutlinerNode) { - subject = subject.parent - parents.push(subject) + subject = subject.parent; + parents.push(subject); } return parents.allAre(parent => { - if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false + if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false; if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) - return false - return true - }) + return false; + return true; + }); }, insertElementIntoUndo( element: OutlinerElement, @@ -211,86 +213,86 @@ export const MirrorModeling = { // pre if (element_before_snapshot) { if (!Undo.current_save.elements[element.uuid]) - Undo.current_save.elements[element.uuid] = element_before_snapshot + Undo.current_save.elements[element.uuid] = element_before_snapshot; } else { if (!Undo.current_save.outliner) - Undo.current_save.outliner = MirrorModeling.outliner_snapshot + Undo.current_save.outliner = MirrorModeling.outliner_snapshot; } // post - if (!element_before_snapshot) undo_aspects.outliner = true - undo_aspects.elements.safePush(element) + if (!element_before_snapshot) undo_aspects.outliner = true; + undo_aspects.elements.safePush(element); }, element_types: {} as Record, registerElementType(type_class: any, options: MirrorModelingElementTypeOptions) { - new Property(type_class, 'boolean', 'allow_mirror_modeling', { default: true }) - let type = type_class.prototype.type - MirrorModeling.element_types[type] = options + new Property(type_class, 'boolean', 'allow_mirror_modeling', { default: true }); + let type = type_class.prototype.type; + MirrorModeling.element_types[type] = options; }, cached_elements: {}, -} +}; interface MirrorModelingElementTypeOptions { - check_parent_symmetry?: boolean - isCentered?(element: OutlinerElement, options?: { center: number }): boolean + check_parent_symmetry?: boolean; + isCentered?(element: OutlinerElement, options?: { center: number }): boolean; getMirroredElement?( element: OutlinerElement, options?: { center: number } - ): OutlinerElement | false - maintainUV?(element: OutlinerElement, original_data: any): void + ): OutlinerElement | false; + maintainUV?(element: OutlinerElement, original_data: any): void; discoverConnectionsPreEdit?(mesh: Mesh): { - faces: Record - vertices: Record - } - updateCounterpart?(original: OutlinerElement, counterpart: OutlinerElement, context: {}): void - createLocalSymmetry?(element: OutlinerElement, cached_data: any): void + faces: Record; + vertices: Record; + }; + updateCounterpart?(original: OutlinerElement, counterpart: OutlinerElement, context: {}): void; + createLocalSymmetry?(element: OutlinerElement, cached_data: any): void; } Blockbench.on('init_edit', ({ aspects }) => { - if (!(BarItems.mirror_modeling as Toggle).value) return + if (!(BarItems.mirror_modeling as Toggle).value) return; - MirrorModeling.initial_transformer_position = Transformer.position.x + MirrorModeling.initial_transformer_position = Transformer.position.x; if (aspects.elements) { - MirrorModeling.cached_elements = {} - MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON() - let edit_side = MirrorModeling.getEditSide() + MirrorModeling.cached_elements = {}; + MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON(); + let edit_side = MirrorModeling.getEditSide(); aspects.elements.forEach(element => { if (element.allow_mirror_modeling) { - let is_centered = MirrorModeling.isCentered(element) + let is_centered = MirrorModeling.isCentered(element); let data = (MirrorModeling.cached_elements[element.uuid] = { is_centered, is_copy: false, counterpart: false as false | OutlinerElement, pre_part_connections: undefined, - }) + }); if (!is_centered) { - data.is_copy = Math.sign(element.getWorldCenter().x) != edit_side - data.counterpart = MirrorModeling.getMirrorElement(element) - if (!data.counterpart) data.is_copy = false + data.is_copy = Math.sign(element.getWorldCenter().x) != edit_side; + data.counterpart = MirrorModeling.getMirrorElement(element); + if (!data.counterpart) data.is_copy = false; } else { if (MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit) { data.pre_part_connections = MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit( element - ) + ); } } } - }) + }); } if (aspects.group || aspects.groups) { - if (!MirrorModeling.cached_elements) MirrorModeling.cached_elements = {} - let selected_groups = aspects.groups ?? [aspects.group] - MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON() + if (!MirrorModeling.cached_elements) MirrorModeling.cached_elements = {}; + let selected_groups = aspects.groups ?? [aspects.group]; + MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON(); // update undo - if (!Undo.current_save.outliner) Undo.current_save.outliner = Outliner.toJSON() - aspects.outliner = true + if (!Undo.current_save.outliner) Undo.current_save.outliner = Outliner.toJSON(); + aspects.outliner = true; selected_groups.forEach(group => { - if (group.origin[0] == (Format.centered_grid ? 0 : 8)) return + if (group.origin[0] == (Format.centered_grid ? 0 : 8)) return; let mirror_group = Group.all.find(g => { if ( @@ -299,28 +301,28 @@ Blockbench.on('init_edit', ({ aspects }) => { Math.epsilon(group.origin[2], g.origin[2]) && group.getDepth() == g.getDepth() ) { - return true + return true; } - }) + }); if (mirror_group) { MirrorModeling.cached_elements[group.uuid] = { counterpart: mirror_group, - } + }; } - }) + }); } -}) +}); Blockbench.on('finish_edit', ({ aspects }) => { - if (!(BarItems.mirror_modeling as Toggle).value) return + if (!(BarItems.mirror_modeling as Toggle).value) return; if (aspects.elements) { - aspects.elements = aspects.elements.slice() - let static_elements_copy = aspects.elements.slice() + aspects.elements = aspects.elements.slice(); + let static_elements_copy = aspects.elements.slice(); static_elements_copy.forEach(element => { - let cached_data = MirrorModeling.cached_elements[element.uuid] + let cached_data = MirrorModeling.cached_elements[element.uuid]; if (element.allow_mirror_modeling && !element.locked) { - let is_centered = MirrorModeling.isCentered(element) + let is_centered = MirrorModeling.isCentered(element); if ( is_centered && @@ -330,57 +332,57 @@ Blockbench.on('finish_edit', ({ aspects }) => { MirrorModeling.element_types[element.type].createLocalSymmetry( element, cached_data - ) + ); } if (is_centered) { - let mirror_element = cached_data?.counterpart + let mirror_element = cached_data?.counterpart; if (mirror_element && mirror_element.uuid != element.uuid) { MirrorModeling.insertElementIntoUndo( mirror_element, Undo.current_save.aspects, mirror_element.getUndoCopy() - ) - mirror_element.remove() - aspects.elements.remove(mirror_element) + ); + mirror_element.remove(); + aspects.elements.remove(mirror_element); } } else { // Construct clone at other side of model - MirrorModeling.createClone(element, aspects) + MirrorModeling.createClone(element, aspects); } } - }) + }); if (aspects.group || aspects.groups || aspects.outliner) { - Canvas.updateAllBones() + Canvas.updateAllBones(); } } else if (aspects.group || aspects.groups) { - let selected_groups = aspects.groups ?? [aspects.group] + let selected_groups = aspects.groups ?? [aspects.group]; selected_groups.forEach(group => { - let mirror_group = MirrorModeling.cached_elements[group.uuid]?.counterpart + let mirror_group = MirrorModeling.cached_elements[group.uuid]?.counterpart; if (mirror_group) { - MirrorModeling.updateParentNodeCounterpart(mirror_group, group) + MirrorModeling.updateParentNodeCounterpart(mirror_group, group); } - }) + }); - aspects.outliner = true - Canvas.updateAllBones() + aspects.outliner = true; + Canvas.updateAllBones(); } -}) +}); // Register element types MirrorModeling.registerElementType(Cube, { isCentered(element: Cube, { center }) { if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) - return false + return false; if (!Math.epsilon(element.to[0], MirrorModeling.flipCoord(element.from[0]), 0.01)) - return false - return true + return false; + return true; }, getMirroredElement(element: Cube, { center }) { - let e = 0.01 - let symmetry_axes = [0] - let off_axes = [1, 2] + let e = 0.01; + let symmetry_axes = [0]; + let off_axes = [1, 2]; if ( symmetry_axes.find( axis => !Math.epsilon(element.from[axis] - center, center - element.to[axis], e) @@ -388,7 +390,7 @@ MirrorModeling.registerElementType(Cube, { off_axes.find(axis => element.rotation[axis]) == undefined && MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element + return element; } else { for (var element2 of Cube.all) { if ( @@ -414,11 +416,11 @@ MirrorModeling.registerElementType(Cube, { axis => !Math.epsilon(element.rotation[axis], element2.rotation[axis], e) ) == undefined ) { - return element2 + return element2; } } } - return false + return false; }, maintainUV(element: Cube, original_data) { element.extend({ @@ -427,22 +429,22 @@ MirrorModeling.registerElementType(Cube, { mirror_uv: original_data.mirror_uv, box_uv: original_data.box_uv, autouv: original_data.autouv, - }) + }); }, -}) +}); MirrorModeling.registerElementType(Mesh, { isCentered(element: Mesh, { center }) { - if (Math.roundTo(element.origin[0], 3) != center) return false + if (Math.roundTo(element.origin[0], 3) != center) return false; if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) - return false - return true + return false; + return true; }, getMirroredElement(element: Mesh, { center }) { - let e = 0.01 - let symmetry_axes = [0] - let off_axes = [1, 2] - let ep = 0.5 - let this_center = element.getCenter(true) + let e = 0.01; + let symmetry_axes = [0]; + let off_axes = [1, 2]; + let ep = 0.5; + let this_center = element.getCenter(true); if ( symmetry_axes.find(axis => !Math.epsilon(element.origin[axis], center, e)) == undefined && @@ -450,12 +452,12 @@ MirrorModeling.registerElementType(Mesh, { off_axes.find(axis => element.rotation[axis]) == undefined && MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element + return element; } else { for (var element2 of Mesh.all) { - let other_center = element2.getCenter(true) + let other_center = element2.getCenter(true); if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) - continue + continue; if ( element2 != element && symmetry_axes.find( @@ -481,21 +483,21 @@ MirrorModeling.registerElementType(Mesh, { axis => !Math.epsilon(this_center[axis], other_center[axis], ep) ) == undefined ) { - return element2 + return element2; } } } - return false + return false; }, maintainUV(element: Mesh, original_data) { for (let fkey in element.faces) { - let face = element.faces[fkey] - let face_before = original_data.faces[fkey] + let face = element.faces[fkey]; + let face_before = original_data.faces[fkey]; if (face_before) { - face.texture = face_before.texture + face.texture = face_before.texture; for (let vkey of face_before.vertices) { if (face.vertices.includes(vkey) && face_before.uv[vkey]) { - face.uv[vkey] = face_before.uv[vkey].slice() + face.uv[vkey] = face_before.uv[vkey].slice(); } } } @@ -505,108 +507,108 @@ MirrorModeling.registerElementType(Mesh, { let data = { faces: {}, vertices: {}, - } + }; // Detect vertex counterparts for (let vkey in mesh.vertices) { - if (data.vertices[vkey]) continue - let vector = Reusable.vec1.fromArray(mesh.vertices[vkey]) - vector.x *= -1 + if (data.vertices[vkey]) continue; + let vector = Reusable.vec1.fromArray(mesh.vertices[vkey]); + vector.x *= -1; for (let vkey2 in mesh.vertices) { //if (vkey == vkey2) continue; - let distance = vector.distanceTo(Reusable.vec2.fromArray(mesh.vertices[vkey2])) + let distance = vector.distanceTo(Reusable.vec2.fromArray(mesh.vertices[vkey2])); if (distance < 0.001) { - data.vertices[vkey] = vkey2 - data.vertices[vkey2] = vkey - break + data.vertices[vkey] = vkey2; + data.vertices[vkey2] = vkey; + break; } } } // Detect face counterparts for (let fkey in mesh.faces) { - if (data.faces[fkey]) continue + if (data.faces[fkey]) continue; for (let fkey2 in mesh.faces) { - if (fkey == fkey2) continue + if (fkey == fkey2) continue; let match = mesh.faces[fkey].vertices.allAre(vkey => { - let other_vkey = data.vertices[vkey] - if (!other_vkey) return false - return mesh.faces[fkey2].vertices.includes(other_vkey) - }) + let other_vkey = data.vertices[vkey]; + if (!other_vkey) return false; + return mesh.faces[fkey2].vertices.includes(other_vkey); + }); if (match) { - data.faces[fkey] = fkey2 - data.faces[fkey2] = fkey - break + data.faces[fkey] = fkey2; + data.faces[fkey2] = fkey; + break; } } } - return data + return data; }, createLocalSymmetry(mesh: Mesh, cached_data) { // Create or update clone - let edit_side = MirrorModeling.getEditSide() - let options = (BarItems.mirror_modeling as Toggle).tool_config.options - let mirror_uv = options.mirror_uv - let pre_part_connections = cached_data?.pre_part_connections + let edit_side = MirrorModeling.getEditSide(); + let options = (BarItems.mirror_modeling as Toggle).tool_config.options; + let mirror_uv = options.mirror_uv; + let pre_part_connections = cached_data?.pre_part_connections; // Delete all vertices on the non-edit side - let deleted_vertices = {} - let deleted_vertices_by_position = {} + let deleted_vertices = {}; + let deleted_vertices_by_position = {}; function positionKey(position) { - return position.map(p => Math.round(p * 25) / 25).join(',') + return position.map(p => Math.round(p * 25) / 25).join(','); } for (let vkey in mesh.vertices) { if (mesh.vertices[vkey][0] && Math.round(mesh.vertices[vkey][0] * edit_side * 50) < 0) { - deleted_vertices[vkey] = mesh.vertices[vkey] - delete mesh.vertices[vkey] - deleted_vertices_by_position[positionKey(deleted_vertices[vkey])] = vkey + deleted_vertices[vkey] = mesh.vertices[vkey]; + delete mesh.vertices[vkey]; + deleted_vertices_by_position[positionKey(deleted_vertices[vkey])] = vkey; } } // Copy existing vertices back to the non-edit side - let added_vertices = [] - let vertex_counterpart = {} - let center_vertices = [] + let added_vertices = []; + let vertex_counterpart = {}; + let center_vertices = []; for (let vkey in mesh.vertices) { - let vertex = mesh.vertices[vkey] + let vertex = mesh.vertices[vkey]; if (Math.abs(mesh.vertices[vkey][0]) < 0.02) { // On Edge - vertex_counterpart[vkey] = vkey - center_vertices.push(vkey) + vertex_counterpart[vkey] = vkey; + center_vertices.push(vkey); } else { let position = [ MirrorModeling.flipCoord(vertex[0]), vertex[1], vertex[2], - ] as ArrayVector3 - let vkey_new = deleted_vertices_by_position[positionKey(position)] + ] as ArrayVector3; + let vkey_new = deleted_vertices_by_position[positionKey(position)]; if (vkey_new) { - mesh.vertices[vkey_new] = position + mesh.vertices[vkey_new] = position; } else { - vkey_new = mesh.addVertices(position)[0] + vkey_new = mesh.addVertices(position)[0]; } - added_vertices.push(vkey_new) - vertex_counterpart[vkey] = vkey_new + added_vertices.push(vkey_new); + vertex_counterpart[vkey] = vkey_new; } } // Delete faces temporarily if all their vertices have been removed - let deleted_faces = {} + let deleted_faces = {}; for (let fkey in mesh.faces) { - let face = mesh.faces[fkey] + let face = mesh.faces[fkey]; let deleted_face_vertices = face.vertices.filter( vkey => deleted_vertices[vkey] || center_vertices.includes(vkey) - ) + ); if ( deleted_face_vertices.length == face.vertices.length && !face.vertices.allAre(vkey => center_vertices.includes(vkey)) ) { - deleted_faces[fkey] = mesh.faces[fkey] - delete mesh.faces[fkey] + deleted_faces[fkey] = mesh.faces[fkey]; + delete mesh.faces[fkey]; } } // Create mirrored faces - let original_fkeys = Object.keys(mesh.faces) + let original_fkeys = Object.keys(mesh.faces); for (let fkey of original_fkeys) { - let face = mesh.faces[fkey] - let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey]) + let face = mesh.faces[fkey]; + let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey]); if ( deleted_face_vertices.length && face.vertices.length != deleted_face_vertices.length * 2 && @@ -616,15 +618,15 @@ MirrorModeling.registerElementType(Mesh, { ) { // cannot flip. restore vertices instead? deleted_face_vertices.forEach(vkey => { - mesh.vertices[vkey] = deleted_vertices[vkey] + mesh.vertices[vkey] = deleted_vertices[vkey]; //delete deleted_vertices[vkey]; - }) + }); } else if (deleted_face_vertices.length) { // face across zero line //let kept_face_keys = face.vertices.filter(vkey => mesh.vertices[vkey] != 0 && !deleted_face_vertices.includes(vkey)); let new_counterparts = face.vertices .filter(vkey => !deleted_vertices[vkey]) - .map(vkey => vertex_counterpart[vkey]) + .map(vkey => vertex_counterpart[vkey]); face.vertices.forEach((vkey, i) => { if (deleted_face_vertices.includes(vkey)) { // Across @@ -632,88 +634,88 @@ MirrorModeling.registerElementType(Mesh, { new_counterparts.sort((a, b) => { let a_distance = Math.pow(mesh.vertices[a][1] - deleted_vertices[vkey][1], 2) + - Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2) + Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2); let b_distance = Math.pow(mesh.vertices[b][1] - deleted_vertices[vkey][1], 2) + - Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2) - return b_distance - a_distance - }) + Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2); + return b_distance - a_distance; + }); - let counterpart = new_counterparts.pop() + let counterpart = new_counterparts.pop(); if (vkey != counterpart && counterpart) { - face.vertices.splice(i, 1, counterpart) - face.uv[counterpart] = face.uv[vkey].slice() as ArrayVector2 - delete face.uv[vkey] + face.vertices.splice(i, 1, counterpart); + face.uv[counterpart] = face.uv[vkey].slice() as ArrayVector2; + delete face.uv[vkey]; } } - }) + }); } else if ( deleted_face_vertices.length == 0 && face.vertices.find(vkey => vkey != vertex_counterpart[vkey]) ) { // Recreate face as mirrored - let new_face_key = pre_part_connections && pre_part_connections.faces[fkey] - let original_face = deleted_faces[new_face_key] + let new_face_key = pre_part_connections && pre_part_connections.faces[fkey]; + let original_face = deleted_faces[new_face_key]; - let new_face = new MeshFace(mesh, face) + let new_face = new MeshFace(mesh, face); face.vertices.forEach((vkey, i) => { - let new_vkey = vertex_counterpart[vkey] - new_face.vertices.splice(i, 1, new_vkey) - delete new_face.uv[vkey] + let new_vkey = vertex_counterpart[vkey]; + new_face.vertices.splice(i, 1, new_vkey); + delete new_face.uv[vkey]; if (mirror_uv || !original_face) { - new_face.uv[new_vkey] = face.uv[vkey].slice() as ArrayVector2 + new_face.uv[new_vkey] = face.uv[vkey].slice() as ArrayVector2; } else { // change - let original_vkey = pre_part_connections.vertices[vkey] + let original_vkey = pre_part_connections.vertices[vkey]; if (original_face.uv[original_vkey]) { - new_face.uv[new_vkey] = original_face.uv[original_vkey].slice() + new_face.uv[new_vkey] = original_face.uv[original_vkey].slice(); } } - }) - new_face.invert() + }); + new_face.invert(); if (new_face_key) { - mesh.faces[new_face_key] = new_face + mesh.faces[new_face_key] = new_face; } else { - ;[new_face_key] = mesh.addFaces(new_face) + [new_face_key] = mesh.addFaces(new_face); } } } - let selected_vertices = mesh.getSelectedVertices(true) - selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey])) - let selected_edges = mesh.getSelectedEdges(true) + let selected_vertices = mesh.getSelectedVertices(true); + selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey])); + let selected_edges = mesh.getSelectedEdges(true); selected_edges.replace( selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey])) - ) - let selected_faces = mesh.getSelectedFaces(true) - selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey])) - - let { preview_controller } = mesh - preview_controller.updateGeometry(mesh) - preview_controller.updateFaces(mesh) - preview_controller.updateUV(mesh) + ); + let selected_faces = mesh.getSelectedFaces(true); + selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey])); + + let { preview_controller } = mesh; + preview_controller.updateGeometry(mesh); + preview_controller.updateFaces(mesh); + preview_controller.updateUV(mesh); }, -}) +}); MirrorModeling.registerElementType(ArmatureBone, { isCentered(element: ArmatureBone, { center }) { - if (Math.roundTo(element.position[0], 3) != center) return false + if (Math.roundTo(element.position[0], 3) != center) return false; if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) - return false - return true + return false; + return true; }, getMirroredElement(element: ArmatureBone, { center }) { - let e = 0.01 - let symmetry_axes = [0] - let off_axes = [1, 2] + let e = 0.01; + let symmetry_axes = [0]; + let off_axes = [1, 2]; if ( symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == undefined && off_axes.find(axis => element.rotation[axis]) == undefined && MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { - return element + return element; } else { for (let element2 of ArmatureBone.all) { - if (element == element2) continue + if (element == element2) continue; if ( symmetry_axes.find( axis => @@ -727,41 +729,41 @@ MirrorModeling.registerElementType(ArmatureBone, { axis => !Math.epsilon(element.position[axis], element2.position[axis], e) ) == undefined ) { - return element2 + return element2; } } } - return false + return false; }, createLocalSymmetry(element: ArmatureBone, cached_data) { - let edit_side = MirrorModeling.getEditSide() - let options = (BarItems.mirror_modeling as Toggle).tool_config.options + let edit_side = MirrorModeling.getEditSide(); + let options = (BarItems.mirror_modeling as Toggle).tool_config.options; // Update vertex weights on centered bones }, updateCounterpart(original, counterpart, context) { // Update vertex weights on off-centered bones }, -}) +}); MirrorModeling.registerElementType(Billboard, { isCentered(element: Billboard, { center }) { - if (Math.roundTo(element.position[0], 3) != center) return false + if (Math.roundTo(element.position[0], 3) != center) return false; //if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - return true + return true; }, getMirroredElement(element: Billboard, { center }) { - let e = 0.01 - let symmetry_axes = [0] - let off_axes = [1, 2] + let e = 0.01; + let symmetry_axes = [0]; + let off_axes = [1, 2]; if ( symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == undefined //off_axes.find(axis => element.rotation[axis]) == undefined ) { - return element + return element; } else { for (let element2 of Billboard.all as Billboard[]) { - if (element == element2) continue + if (element == element2) continue; if ( symmetry_axes.find( axis => @@ -775,18 +777,18 @@ MirrorModeling.registerElementType(Billboard, { axis => !Math.epsilon(element.position[axis], element2.position[axis], e) ) == undefined ) { - return element2 + return element2; } } } - return false + return false; }, maintainUV(element: Billboard, original_data) { element.extend({ faces: original_data.faces, - }) + }); }, -}) +}); BARS.defineActions(() => { let toggle = new Toggle('mirror_modeling', { @@ -794,9 +796,9 @@ BARS.defineActions(() => { category: 'edit', condition: { modes: ['edit'] }, onChange() { - Project.mirror_modeling_enabled = this.value - MirrorModeling.cached_elements = {} - updateSelection() + Project.mirror_modeling_enabled = this.value; + MirrorModeling.cached_elements = {}; + updateSelection(); }, tool_config: new ToolConfig('mirror_modeling_options', { title: 'action.mirror_modeling', @@ -809,15 +811,15 @@ BARS.defineActions(() => { }, }, onOpen() { - this.setFormValues({ enabled: toggle.value }, false) + this.setFormValues({ enabled: toggle.value }, false); }, onFormChange(formResult) { if (toggle.value != formResult.enabled) { - toggle.trigger() + toggle.trigger(); } }, }), - }) + }); let allow_toggle = new Toggle('allow_element_mirror_modeling', { icon: 'align_horizontal_center', category: 'edit', @@ -825,33 +827,33 @@ BARS.defineActions(() => { onChange(value) { Outliner.selected.forEach(element => { if ('allow_mirror_modeling' in (element.constructor as any).properties == false) - return + return; // @ts-ignore - element.allow_mirror_modeling = value - }) + element.allow_mirror_modeling = value; + }); }, - }) + }); Blockbench.on('update_selection', () => { - if (!Condition(allow_toggle.condition)) return + if (!Condition(allow_toggle.condition)) return; // @ts-ignore - let disabled = Outliner.selected.find(el => el.allow_mirror_modeling === false) + let disabled = Outliner.selected.find(el => el.allow_mirror_modeling === false); if (allow_toggle.value != !disabled) { - allow_toggle.value = !disabled - allow_toggle.updateEnabledState() + allow_toggle.value = !disabled; + allow_toggle.updateEnabledState(); } - }) + }); new Action('apply_mirror_modeling', { icon: 'align_horizontal_right', category: 'edit', condition: { modes: ['edit'] }, click() { - let value_before = toggle.value - toggle.value = true - Undo.initEdit({ elements: Outliner.selected, groups: Group.selected }) - Undo.finishEdit('Applied mirror modeling') - toggle.value = value_before + let value_before = toggle.value; + toggle.value = true; + Undo.initEdit({ elements: Outliner.selected, groups: Group.selected }); + Undo.finishEdit('Applied mirror modeling'); + toggle.value = value_before; }, - }) -}) + }); +}); -Object.assign(window, { MirrorModeling }) +Object.assign(window, { MirrorModeling }); diff --git a/js/modeling/weight_paint.ts b/js/modeling/weight_paint.ts index 5c5db2d26..60412cd53 100644 --- a/js/modeling/weight_paint.ts +++ b/js/modeling/weight_paint.ts @@ -1,62 +1,62 @@ -import { Blockbench } from '../api' -import { THREE } from '../lib/libs' -import { Armature } from '../outliner/armature' -import { ArmatureBone } from '../outliner/armature_bone' -import { Preview } from '../preview/preview' +import { Blockbench } from '../api'; +import { THREE } from '../lib/libs'; +import { Armature } from '../outliner/armature'; +import { ArmatureBone } from '../outliner/armature_bone'; +import { Preview } from '../preview/preview'; type CanvasClickData = | { event: MouseEvent } | { - event: MouseEvent - element: OutlinerElement - face: string - intersects: Array> - } + event: MouseEvent; + element: OutlinerElement; + face: string; + intersects: Array>; + }; -let brush_outline: HTMLElement +let brush_outline: HTMLElement; function updateBrushOutline(event: PointerEvent) { - if (!brush_outline) return - let preview = Preview.selected as Preview - let preview_offset = $(preview.canvas).offset() - let click_pos = [event.clientX - preview_offset.left, event.clientY - preview_offset.top] - preview.node.append(brush_outline) - brush_outline.style.left = click_pos[0] + 'px' - brush_outline.style.top = click_pos[1] + 'px' + if (!brush_outline) return; + let preview = Preview.selected as Preview; + let preview_offset = $(preview.canvas).offset(); + let click_pos = [event.clientX - preview_offset.left, event.clientY - preview_offset.top]; + preview.node.append(brush_outline); + brush_outline.style.left = click_pos[0] + 'px'; + brush_outline.style.top = click_pos[1] + 'px'; } -let screen_space_vertex_positions: null | Record = null -const raycaster = new THREE.Raycaster() +let screen_space_vertex_positions: null | Record = null; +const raycaster = new THREE.Raycaster(); function updateScreenSpaceVertexPositions(mesh: Mesh) { - if (screen_space_vertex_positions) return screen_space_vertex_positions + if (screen_space_vertex_positions) return screen_space_vertex_positions; - const depth_check = (BarItems.weight_brush_xray as Toggle).value == false - let vec = new THREE.Vector3() - raycaster.ray.origin.setFromMatrixPosition(Preview.selected.camera.matrixWorld) - let raycasts = 0 + const depth_check = (BarItems.weight_brush_xray as Toggle).value == false; + let vec = new THREE.Vector3(); + raycaster.ray.origin.setFromMatrixPosition(Preview.selected.camera.matrixWorld); + let raycasts = 0; - screen_space_vertex_positions = {} + screen_space_vertex_positions = {}; for (let vkey in mesh.vertices) { - let pos = mesh.mesh.localToWorld(vec.fromArray(mesh.vertices[vkey])) + let pos = mesh.mesh.localToWorld(vec.fromArray(mesh.vertices[vkey])); if (depth_check) { - raycaster.ray.direction.copy(pos).sub(raycaster.ray.origin) - const z_distance = raycaster.ray.direction.length() - raycaster.ray.direction.normalize() - let intersection = raycaster.intersectObject(mesh.mesh, false)[0] - raycasts++ + raycaster.ray.direction.copy(pos).sub(raycaster.ray.origin); + const z_distance = raycaster.ray.direction.length(); + raycaster.ray.direction.normalize(); + let intersection = raycaster.intersectObject(mesh.mesh, false)[0]; + raycasts++; if (intersection && intersection.distance < z_distance - 0.001) { - continue + continue; } } - let screen_pos = Preview.selected.vectorToScreenPosition(pos.clone()) - screen_space_vertex_positions[vkey] = screen_pos + let screen_pos = Preview.selected.vectorToScreenPosition(pos.clone()); + screen_space_vertex_positions[vkey] = screen_pos; } - return screen_space_vertex_positions + return screen_space_vertex_positions; } Blockbench.on('update_camera_position', () => { - screen_space_vertex_positions = null -}) + screen_space_vertex_positions = null; +}); new Tool('weight_brush', { icon: 'stylus_highlighter', @@ -70,68 +70,68 @@ new Tool('weight_brush', { condition: { modes: ['edit'], method: () => Armature.all.length }, onCanvasClick(data: CanvasClickData) { - if ('element' in data == false) return - let preview = Preview.selected as Preview - let preview_offset = $(preview.canvas).offset() - let armature_bone = ArmatureBone.selected[0] as ArmatureBone - let other_bones = armature_bone.getArmature().getAllBones() as ArmatureBone[] - other_bones.remove(armature_bone) + if ('element' in data == false) return; + let preview = Preview.selected as Preview; + let preview_offset = $(preview.canvas).offset(); + let armature_bone = ArmatureBone.selected[0] as ArmatureBone; + let other_bones = armature_bone.getArmature().getAllBones() as ArmatureBone[]; + other_bones.remove(armature_bone); if (!armature_bone) { - return Blockbench.showQuickMessage('Select an armature bone first!') + return Blockbench.showQuickMessage('Select an armature bone first!'); } if (data.element instanceof Mesh == false) { - return + return; } if (!data.element.getArmature()) { - return Blockbench.showQuickMessage('This mesh is not attached to an armature!') + return Blockbench.showQuickMessage('This mesh is not attached to an armature!'); } - let undo_tracked = [armature_bone] - Undo.initEdit({ elements: undo_tracked }) + let undo_tracked = [armature_bone]; + Undo.initEdit({ elements: undo_tracked }); - let last_click_pos = [0, 0] + let last_click_pos = [0, 0]; const draw = (event: MouseEvent, data?: CanvasClickData | false) => { - let radius = (BarItems.slider_weight_brush_size as NumSlider).get() + let radius = (BarItems.slider_weight_brush_size as NumSlider).get(); let click_pos = [ event.clientX - preview_offset.left, event.clientY - preview_offset.top, - ] - let subtract = event.ctrlOrCmd || Pressing.overrides.ctrl + ]; + let subtract = event.ctrlOrCmd || Pressing.overrides.ctrl; if ( Math.pow(last_click_pos[0] - click_pos[0], 2) + Math.pow(last_click_pos[1] - click_pos[1], 2) < 30 ) { - return + return; } - last_click_pos = click_pos + last_click_pos = click_pos; - data = data ?? preview.raycast(event) - if (!data || 'element' in data == false) return - let mesh = data.element - if (mesh instanceof Mesh == false) return - let vec = new THREE.Vector2() + data = data ?? preview.raycast(event); + if (!data || 'element' in data == false) return; + let mesh = data.element; + if (mesh instanceof Mesh == false) return; + let vec = new THREE.Vector2(); - updateScreenSpaceVertexPositions(mesh) + updateScreenSpaceVertexPositions(mesh); for (let vkey in mesh.vertices) { - let screen_pos = screen_space_vertex_positions[vkey] - if (!screen_pos) continue + let screen_pos = screen_space_vertex_positions[vkey]; + if (!screen_pos) continue; let distance = vec .set(screen_pos.x - click_pos[0], screen_pos.y - click_pos[1]) - .length() - let base_radius = 0.2 - let falloff = (1 - distance / radius) * (1 + base_radius) - let influence = Math.hermiteBlend(Math.clamp(falloff, 0, 1)) - let value = armature_bone.vertex_weights[vkey] ?? 0 + .length(); + let base_radius = 0.2; + let falloff = (1 - distance / radius) * (1 + base_radius); + let influence = Math.hermiteBlend(Math.clamp(falloff, 0, 1)); + let value = armature_bone.vertex_weights[vkey] ?? 0; if (event.shiftKey || Pressing.overrides.shift) { - influence /= 8 + influence /= 8; } if (subtract) { - value = value * (1 - influence) + value = value * (1 - influence); } else { - value = value + (1 - value) * influence + value = value + (1 - value) * influence; } // Reduce weight on other bones @@ -141,59 +141,59 @@ new Tool('weight_brush', { bone.vertex_weights[vkey] - influence, 0, 1 - ) + ); if (Undo.current_save && !undo_tracked.includes(bone)) { - Undo.current_save.addElements([bone]) - undo_tracked.push(bone) + Undo.current_save.addElements([bone]); + undo_tracked.push(bone); } } } if (value < 0.04) { - delete armature_bone.vertex_weights[vkey] + delete armature_bone.vertex_weights[vkey]; } else { - armature_bone.vertex_weights[vkey] = value + armature_bone.vertex_weights[vkey] = value; } } // @ts-ignore - Mesh.preview_controller.updateGeometry(mesh) - } + Mesh.preview_controller.updateGeometry(mesh); + }; const stop = (event: MouseEvent) => { - document.removeEventListener('pointermove', draw) - document.removeEventListener('pointerup', stop) - - Undo.finishEdit('Paint vertex weights') - } - document.addEventListener('pointermove', draw) - document.addEventListener('pointerup', stop) - draw(data.event, data) + document.removeEventListener('pointermove', draw); + document.removeEventListener('pointerup', stop); + + Undo.finishEdit('Paint vertex weights'); + }; + document.addEventListener('pointermove', draw); + document.addEventListener('pointerup', stop); + draw(data.event, data); }, onSelect() { - Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }) - ;(BarItems.slider_weight_brush_size as NumSlider).update() - Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.subtract') - Interface.addSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity') + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }); + (BarItems.slider_weight_brush_size as NumSlider).update(); + Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); + Interface.addSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); // @ts-ignore ArmatureBone.preview_controller.material.wireframe = - ArmatureBone.preview_controller.material_selected.wireframe = true + ArmatureBone.preview_controller.material_selected.wireframe = true; - brush_outline = Interface.createElement('div', { id: 'weight_brush_outline' }) - document.addEventListener('pointermove', updateBrushOutline) + brush_outline = Interface.createElement('div', { id: 'weight_brush_outline' }); + document.addEventListener('pointermove', updateBrushOutline); }, onUnselect() { setTimeout(() => { - Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }) - }, 0) - Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.subtract') - Interface.removeSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity') + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }); + }, 0); + Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); + Interface.removeSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); // @ts-ignore ArmatureBone.preview_controller.material.wireframe = - ArmatureBone.preview_controller.material_selected.wireframe = false + ArmatureBone.preview_controller.material_selected.wireframe = false; - if (brush_outline) brush_outline.remove() - document.removeEventListener('pointermove', updateBrushOutline) + if (brush_outline) brush_outline.remove(); + document.removeEventListener('pointermove', updateBrushOutline); }, -}) +}); let slider = new NumSlider('slider_weight_brush_size', { condition: () => Toolbox?.selected?.id == 'weight_brush', tool_setting: 'weight_brush_size', @@ -204,19 +204,19 @@ let slider = new NumSlider('slider_weight_brush_size', { interval: 1, default: 50, }, -}) +}); slider.on('change', (data: { number: number }) => { if (brush_outline) { - brush_outline.style.setProperty('--radius', data.number.toString()) + brush_outline.style.setProperty('--radius', data.number.toString()); } -}) +}); new Toggle('weight_brush_xray', { icon: 'disabled_visible', category: 'edit', condition: () => Toolbox?.selected?.id == 'weight_brush', -}) +}); -const vertex_weight_view_modes = ['vertex_weight', 'weighted_bone_colors'] +const vertex_weight_view_modes = ['vertex_weight', 'weighted_bone_colors']; function updateWeightPreview() { if ( Toolbox.selected.id == 'weight_brush' || @@ -225,8 +225,8 @@ function updateWeightPreview() { Canvas.updateView({ elements: Mesh.all.filter(mesh => mesh.getArmature()), element_aspects: { geometry: true }, - }) - if (Modes.animate) Animator.preview() + }); + if (Modes.animate) Animator.preview(); } } -Blockbench.on('update_selection', updateWeightPreview) +Blockbench.on('update_selection', updateWeightPreview); diff --git a/js/modes.ts b/js/modes.ts index 1458a9b08..c2868a2da 100644 --- a/js/modes.ts +++ b/js/modes.ts @@ -1,179 +1,179 @@ -import { Vue } from './lib/libs' -import { Blockbench } from './api' -import { Interface, Panels } from './interface/interface' -import { MenuBar } from './interface/menu_bar' -import { updatePanelSelector, updateSidebarOrder } from './interface/panels' -import { Prop } from './misc' -import { Outliner } from './outliner/outliner' -import { ReferenceImage } from './preview/reference_images' +import { Vue } from './lib/libs'; +import { Blockbench } from './api'; +import { Interface, Panels } from './interface/interface'; +import { MenuBar } from './interface/menu_bar'; +import { updatePanelSelector, updateSidebarOrder } from './interface/panels'; +import { Prop } from './misc'; +import { Outliner } from './outliner/outliner'; +import { ReferenceImage } from './preview/reference_images'; interface ModeOptions { - id?: string - name?: string - icon?: string - default_tool?: string - selectElements?: boolean - category?: string + id?: string; + name?: string; + icon?: string; + default_tool?: string; + selectElements?: boolean; + category?: string; /** * Hide certain types of nodes in the outliner, like cubes and meshes in animation mode */ - hidden_node_types?: string[] - hide_toolbars?: boolean - hide_sidebars?: boolean - hide_status_bar?: boolean - condition?: ConditionResolvable - component?: Vue.Component - onSelect?(): void - onUnselect?(): void + hidden_node_types?: string[]; + hide_toolbars?: boolean; + hide_sidebars?: boolean; + hide_status_bar?: boolean; + condition?: ConditionResolvable; + component?: Vue.Component; + onSelect?(): void; + onUnselect?(): void; } export class Mode extends KeybindItem { - id: string - name: string - icon: string - selected: boolean - tool: string - default_tool?: string - selectElements: boolean - hidden_node_types: string[] - hide_toolbars: boolean - hide_sidebars: boolean - hide_status_bar: boolean - vue?: Vue - - onSelect?: () => void - onUnselect?: () => void + id: string; + name: string; + icon: string; + selected: boolean; + tool: string; + default_tool?: string; + selectElements: boolean; + hidden_node_types: string[]; + hide_toolbars: boolean; + hide_sidebars: boolean; + hide_status_bar: boolean; + vue?: Vue; + + onSelect?: () => void; + onUnselect?: () => void; constructor(id: string, data: ModeOptions) { if (typeof id == 'object') { - data = id - id = data.id + data = id; + id = data.id; } // @ts-ignore - super(id, data) - this.id = id - this.name = data.name || tl('mode.' + this.id) - this.icon = data.icon || 'video_label' - this.selected = false - - this.default_tool = data.default_tool - this.selectElements = data.selectElements !== false + super(id, data); + this.id = id; + this.name = data.name || tl('mode.' + this.id); + this.icon = data.icon || 'video_label'; + this.selected = false; + + this.default_tool = data.default_tool; + this.selectElements = data.selectElements !== false; this.hidden_node_types = - data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : [] + data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : []; - this.hide_toolbars = data.hide_toolbars - this.hide_sidebars = data.hide_sidebars - this.hide_status_bar = data.hide_status_bar + this.hide_toolbars = data.hide_toolbars; + this.hide_sidebars = data.hide_sidebars; + this.hide_status_bar = data.hide_status_bar; - this.condition = data.condition - this.onSelect = data.onSelect - this.onUnselect = data.onUnselect + this.condition = data.condition; + this.onSelect = data.onSelect; + this.onUnselect = data.onUnselect; - Modes.options[this.id] = this + Modes.options[this.id] = this; if (data.component) { - let node = document.createElement('div') - let mount = document.createElement('div') - node.id = 'mode_screen_' + this.id - node.appendChild(mount) - document.getElementById('center').appendChild(node) - - this.vue = new Vue(data.component) - this.vue.$mount(mount) + let node = document.createElement('div'); + let mount = document.createElement('div'); + node.id = 'mode_screen_' + this.id; + node.appendChild(mount); + document.getElementById('center').appendChild(node); + + this.vue = new Vue(data.component); + this.vue.$mount(mount); } } /**Selects the mode */ select() { if (Modes.selected instanceof Mode) { - Modes.selected.unselect() + Modes.selected.unselect(); } - this.selected = true - Mode.selected = this - Modes.selected = this - Modes[Modes.selected.id] = true - if (Project) Project.mode = this.id + this.selected = true; + Mode.selected = this; + Modes.selected = this; + Modes[Modes.selected.id] = true; + if (Project) Project.mode = this.id; - document.body.setAttribute('mode', this.id) + document.body.setAttribute('mode', this.id); if (MenuBar.mode_switcher_button) { - let icon = Blockbench.getIconNode(this.icon) - MenuBar.mode_switcher_button.firstChild.replaceWith(icon) - MenuBar.mode_switcher_button.classList.remove('hidden') + let icon = Blockbench.getIconNode(this.icon); + MenuBar.mode_switcher_button.firstChild.replaceWith(icon); + MenuBar.mode_switcher_button.classList.remove('hidden'); } $('#main_toolbar .toolbar_wrapper').css( 'visibility', this.hide_toolbars ? 'hidden' : 'visible' - ) - $('#status_bar').css('display', this.hide_status_bar ? 'none' : 'flex') + ); + $('#status_bar').css('display', this.hide_status_bar ? 'none' : 'flex'); - Outliner.vue.options.hidden_types.replace(this.hidden_node_types) + Outliner.vue.options.hidden_types.replace(this.hidden_node_types); if (typeof this.onSelect === 'function') { - this.onSelect() + this.onSelect(); } - updatePanelSelector() - ReferenceImage.updateAll() + updatePanelSelector(); + ReferenceImage.updateAll(); if ( Interface.Panels[Prop.active_panel] && !Condition(Interface.Panels[Prop.active_panel].condition) ) { - Prop.active_panel = 'preview' + Prop.active_panel = 'preview'; } - UVEditor.beforeMoving() + UVEditor.beforeMoving(); if (!Blockbench.isMobile) { for (let id in Panels) { - let panel = Panels[id] - panel.updatePositionData() - panel.updateSlot() + let panel = Panels[id]; + panel.updatePositionData(); + panel.updateSlot(); } - updateSidebarOrder() + updateSidebarOrder(); } - Canvas.updateRenderSides() - let selected_tool = BarItems[this.tool] instanceof Tool && BarItems[this.tool] + Canvas.updateRenderSides(); + let selected_tool = BarItems[this.tool] instanceof Tool && BarItems[this.tool]; let default_tool = - BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool] + BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool]; if (selected_tool instanceof Tool && Condition(selected_tool.condition)) { - selected_tool.select() + selected_tool.select(); } else if (default_tool instanceof Tool) { - if (default_tool != Toolbox.selected) default_tool.select() + if (default_tool != Toolbox.selected) default_tool.select(); } else { - if (BarItems.move_tool != Toolbox.selected) (BarItems.move_tool as Tool).select() + if (BarItems.move_tool != Toolbox.selected) (BarItems.move_tool as Tool).select(); } - updateInterface() - updateSelection() - Blockbench.dispatchEvent('select_mode', { mode: this }) + updateInterface(); + updateSelection(); + Blockbench.dispatchEvent('select_mode', { mode: this }); } /**Unselects the mode */ unselect() { - delete Modes[this.id] - Modes.previous_id = this.id + delete Modes[this.id]; + Modes.previous_id = this.id; if (typeof this.onUnselect === 'function') { - Blockbench.dispatchEvent('unselect_mode', { mode: this }) - this.onUnselect() + Blockbench.dispatchEvent('unselect_mode', { mode: this }); + this.onUnselect(); } - this.selected = false - Mode.selected = Modes.selected = false + this.selected = false; + Mode.selected = Modes.selected = false; } /**Activates the mode */ trigger() { if (Condition(this.condition)) { - this.select() + this.select(); } } delete() { if (Mode.selected == this) { - Modes.options.edit.select() + Modes.options.edit.select(); } - delete Modes.options[this.id] + delete Modes.options[this.id]; } - static selected = null + static selected = null; } export const Modes = { get id() { - return Mode.selected ? Mode.selected.id : '' + return Mode.selected ? Mode.selected.id : ''; }, vue: null as Vue | null, selected: false as boolean | Mode, @@ -185,24 +185,24 @@ export const Modes = { paint: false, pose: false, mobileModeMenu(button, event) { - let entries = [] + let entries = []; for (let id in Modes.options) { - let mode = Modes.options[id] + let mode = Modes.options[id]; let entry = { id, icon: mode.icon || 'mode', name: mode.name, condition: mode.condition, click: () => { - mode.select() + mode.select(); }, - } - entries.push(entry) + }; + entries.push(entry); } - let menu = new Menu(entries).open(button) - return menu + let menu = new Menu(entries).open(button); + return menu; }, -} +}; onVueSetup(function () { if (!Blockbench.isMobile) { Modes.vue = new Vue({ @@ -212,21 +212,21 @@ onVueSetup(function () { }, methods: { showModes() { - let count = 0 + let count = 0; for (let key in this.options) { - if (Condition(this.options[key].condition)) count++ + if (Condition(this.options[key].condition)) count++; } - return count > 1 + return count > 1; }, Condition, }, - }) + }); } else { - document.getElementById('mode_selector').remove() + document.getElementById('mode_selector').remove(); } -}) +}); Object.assign(window, { Mode, Modes, -}) +}); diff --git a/js/native_apis.ts b/js/native_apis.ts index 5c7044d90..b8990704d 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -1,19 +1,19 @@ -import { BBPlugin } from './plugin_loader' -import { createScopedFS } from './util/scoped_fs' +import { BBPlugin } from './plugin_loader'; +import { createScopedFS } from './util/scoped_fs'; -const electron: typeof import('@electron/remote') = require('@electron/remote') +const electron: typeof import('@electron/remote') = require('@electron/remote'); const { clipboard, shell, nativeImage, ipcRenderer, webUtils } = - require('electron') as typeof import('electron') -const app = electron.app -const fs: typeof import('node:fs') = require('node:fs') -const NodeBuffer: typeof import('node:buffer') = require('buffer') -const zlib: typeof import('node:zlib') = require('zlib') -const child_process: typeof import('node:child_process') = require('child_process') -const https: typeof import('node:https') = require('https') -const PathModule: typeof import('node:path') = require('path') -const os: typeof import('node:os') = require('os') -const currentwindow = electron.getCurrentWindow() -const dialog = electron.dialog + require('electron') as typeof import('electron'); +const app = electron.app; +const fs: typeof import('node:fs') = require('node:fs'); +const NodeBuffer: typeof import('node:buffer') = require('buffer'); +const zlib: typeof import('node:zlib') = require('zlib'); +const child_process: typeof import('node:child_process') = require('child_process'); +const https: typeof import('node:https') = require('https'); +const PathModule: typeof import('node:path') = require('path'); +const os: typeof import('node:os') = require('os'); +const currentwindow = electron.getCurrentWindow(); +const dialog = electron.dialog; /** @internal */ export { @@ -34,15 +34,15 @@ export { dialog, Buffer, nativeImage, -} +}; /** * @internal */ -export const process = window.process -delete window.process +export const process = window.process; +delete window.process; -const { stringify, parse } = JSON +const { stringify, parse } = JSON; const SAFE_APIS = [ 'path', @@ -53,7 +53,7 @@ const SAFE_APIS = [ 'url', 'string_decoder', 'querystring', -] +]; const REQUESTABLE_APIS = [ 'fs', 'process', @@ -64,7 +64,7 @@ const REQUESTABLE_APIS = [ 'util', 'os', 'v8', -] +]; const API_DESCRIPTIONS = { fs: 'access and change files on your computer', process: 'access to the process running Blockbench', @@ -73,25 +73,25 @@ const API_DESCRIPTIONS = { os: 'see information about your computer', https: 'create servers and talk to other servers', dialog: 'open native dialogs', -} +}; type PluginPermissions = { - allowed: Record -} -const PLUGIN_SETTINGS_PATH = PathModule.join(app.getPath('userData'), 'plugin_permissions.json') -const PluginSettings: Record = {} + allowed: Record; +}; +const PLUGIN_SETTINGS_PATH = PathModule.join(app.getPath('userData'), 'plugin_permissions.json'); +const PluginSettings: Record = {}; try { - let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, { encoding: 'utf-8' }) - let data = parse(content) + let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, { encoding: 'utf-8' }); + let data = parse(content); if (typeof data == 'object') { - Object.assign(PluginSettings, data) + Object.assign(PluginSettings, data); } } catch (err) {} function savePluginSettings() { - fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), { encoding: 'utf-8' }) + fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), { encoding: 'utf-8' }); } interface GetModuleOptions { - scope?: string - message?: string + scope?: string; + message?: string; } function getModule( module_name: string, @@ -99,32 +99,32 @@ function getModule( plugin: InstanceType, options: GetModuleOptions = {} ) { - const no_namespace_name = module_name.replace(/^node:/, '') + const no_namespace_name = module_name.replace(/^node:/, ''); if (SAFE_APIS.includes(no_namespace_name)) { - return originalRequire(module_name) + return originalRequire(module_name); } if (!REQUESTABLE_APIS.includes(no_namespace_name)) { - throw `The module "${module_name}" is not supported` + throw `The module "${module_name}" is not supported`; } - const options2: GetModuleOptions = {} + const options2: GetModuleOptions = {}; for (let key in options) { - options2[key] = options[key] + options2[key] = options[key]; } - let permission = PluginSettings[plugin_id]?.allowed[module_name] - let has_permission = false + let permission = PluginSettings[plugin_id]?.allowed[module_name]; + let has_permission = false; if (permission === true) { - has_permission = true + has_permission = true; } else if (module_name == 'fs' && permission?.directories?.includes(options2.scope)) { - has_permission = true + has_permission = true; } if (!has_permission) { - let api_description = API_DESCRIPTIONS[module_name] ?? `the module "${module_name}"` - let option_text = '' + let api_description = API_DESCRIPTIONS[module_name] ?? `the module "${module_name}"`; + let option_text = ''; if (module_name == 'fs' && options2.scope) { - api_description = 'a folder' - option_text = '\nLocation: "' + options2.scope.replace(/\n/g, '') + '"' + api_description = 'a folder'; + option_text = '\nLocation: "' + options2.scope.replace(/\n/g, '') + '"'; } let result = dialog.showMessageBoxSync(currentwindow, { @@ -135,7 +135,7 @@ function getModule( noLink: true, cancelId: 3, buttons: ['Allow once', 'Always allow for this plugin', 'Uninstall plugin', 'Deny'], - }) + }); enum Result { Once = 0, Always = 1, @@ -147,67 +147,67 @@ function getModule( if (!PluginSettings[plugin_id]?.allowed) { PluginSettings[plugin_id] = { allowed: {}, - } + }; } - let allowed = PluginSettings[plugin_id].allowed + let allowed = PluginSettings[plugin_id].allowed; if (module_name == 'fs' && options2.scope) { if (typeof allowed[module_name] != 'object') - allowed[module_name] = { directories: [] } - allowed[module_name].directories.push(options2.scope) + allowed[module_name] = { directories: [] }; + allowed[module_name].directories.push(options2.scope); } else { - allowed[module_name] = true + allowed[module_name] = true; } - savePluginSettings() + savePluginSettings(); } if (result == Result.Uninstall) { setTimeout(() => { - plugin.uninstall() - }, 20) + plugin.uninstall(); + }, 20); } if (!(result == Result.Once || result == Result.Always)) { - console.warn(`User denied access to "${module_name}" module`) - return + console.warn(`User denied access to "${module_name}" module`); + return; } - console.warn(`Gave plugin ${plugin_id} access to module ${module_name}`) + console.warn(`Gave plugin ${plugin_id} access to module ${module_name}`); } if (no_namespace_name == 'fs') { - return createScopedFS(options2.scope) + return createScopedFS(options2.scope); } else if (no_namespace_name == 'process') { - return process + return process; } else if (no_namespace_name == 'dialog') { - let api = {} + let api = {}; for (let key in dialog) { - api[key] = (options: any) => dialog[key](currentwindow, options) + api[key] = (options: any) => dialog[key](currentwindow, options); } - return api + return api; } - return require(module_name) + return require(module_name); } /** * @internal */ export function getPluginScopedRequire(plugin: InstanceType) { - const plugin_id = plugin.id + const plugin_id = plugin.id; return function require(module_id: string, options?: GetModuleOptions) { - return getModule(module_id, plugin_id, plugin, options) - } + return getModule(module_id, plugin_id, plugin, options); + }; } -const originalRequire = window.require -delete window.require +const originalRequire = window.require; +delete window.require; export function revokePluginPermissions(plugin: InstanceType): string[] { - let permissions = Object.keys(PluginSettings[plugin.id]?.allowed ?? {}) - delete PluginSettings[plugin.id] - savePluginSettings() - return permissions + let permissions = Object.keys(PluginSettings[plugin.id]?.allowed ?? {}); + delete PluginSettings[plugin.id]; + savePluginSettings(); + return permissions; } export function getPluginPermissions(plugin: InstanceType) { - let data = PluginSettings[plugin.id]?.allowed - if (data) return parse(stringify(data)) as Record + let data = PluginSettings[plugin.id]?.allowed; + if (data) return parse(stringify(data)) as Record; } export const SystemInfo = { @@ -216,29 +216,29 @@ export const SystemInfo = { appdata_directory: electron.process.env.APPDATA, arch: process.arch, os_version: os.version(), -} +}; /** * @internal */ export function getPCUsername() { - return process.env.USERNAME + return process.env.USERNAME; } /** * @internal */ export function openFileInEditor(file_path: string, editor: string) { if (SystemInfo.platform == 'darwin') { - child_process.exec(`open '${file_path}' -a '${editor}'`) + child_process.exec(`open '${file_path}' -a '${editor}'`); } else { - child_process.spawn(editor, [file_path]) + child_process.spawn(editor, [file_path]); } } Object.assign(window, { SystemInfo, Buffer, -}) +}); /** * TODO: diff --git a/js/native_apis_web.ts b/js/native_apis_web.ts index ec290bf8a..29300481a 100644 --- a/js/native_apis_web.ts +++ b/js/native_apis_web.ts @@ -1,6 +1,6 @@ // Dummy exports for native APIs when running on web -const NULL = null +const NULL = null; /** @internal */ export { NULL as electron, @@ -24,11 +24,11 @@ export { NULL as process, NULL as SystemInfo, NULL as revokePluginPermissions, -} +}; /** * @internal */ export function getPCUsername() { - return '' + return ''; } diff --git a/js/outliner/armature.ts b/js/outliner/armature.ts index 7b112e276..94e4c7991 100644 --- a/js/outliner/armature.ts +++ b/js/outliner/armature.ts @@ -1,98 +1,98 @@ -import { Blockbench } from '../api' -import { THREE, Vue } from '../lib/libs' -import { ArmatureBone } from './armature_bone' +import { Blockbench } from '../api'; +import { THREE, Vue } from '../lib/libs'; +import { ArmatureBone } from './armature_bone'; interface ArmatureOptions { - name?: string - export?: boolean - locked?: boolean - visibility?: boolean + name?: string; + export?: boolean; + locked?: boolean; + visibility?: boolean; } export class Armature extends OutlinerElement { - children: ArmatureBone[] - isOpen: boolean - visibility: boolean - origin: ArrayVector3 + children: ArmatureBone[]; + isOpen: boolean; + visibility: boolean; + origin: ArrayVector3; - static preview_controller: NodePreviewController + static preview_controller: NodePreviewController; constructor(data?: ArmatureOptions, uuid?: UUID) { - super(data, uuid) + super(data, uuid); for (let key in Armature.properties) { - Armature.properties[key].reset(this) + Armature.properties[key].reset(this); } - this.name = 'armature' - this.children = [] - this.selected = false - this.locked = false - this.export = true - this.parent = 'root' - this.isOpen = false - this.visibility = true - this.origin = [0, 0, 0] + this.name = 'armature'; + this.children = []; + this.selected = false; + this.locked = false; + this.export = true; + this.parent = 'root'; + this.isOpen = false; + this.visibility = true; + this.origin = [0, 0, 0]; if (typeof data === 'object') { - this.extend(data) + this.extend(data); } else if (typeof data === 'string') { - this.name = data + this.name = data; } } extend(object: ArmatureOptions) { for (let key in Armature.properties) { - Armature.properties[key].merge(this, object) + Armature.properties[key].merge(this, object); } - Merge.string(this, object, 'name') - this.sanitizeName() - Merge.boolean(this, object, 'export') - Merge.boolean(this, object, 'locked') - Merge.boolean(this, object, 'visibility') - return this + Merge.string(this, object, 'name'); + this.sanitizeName(); + Merge.boolean(this, object, 'export'); + Merge.boolean(this, object, 'locked'); + Merge.boolean(this, object, 'visibility'); + return this; } getMesh() { - return this.mesh + return this.mesh; } init() { - super.init() + super.init(); if (!this.mesh || !this.mesh.parent) { // @ts-ignore - this.constructor.preview_controller.setup(this) + this.constructor.preview_controller.setup(this); } - return this + return this; } markAsSelected(descendants: boolean = false) { - Outliner.selected.safePush(this) - this.selected = true + Outliner.selected.safePush(this); + this.selected = true; if (descendants) { - this.children.forEach(child => child.markAsSelected(true)) + this.children.forEach(child => child.markAsSelected(true)); } - TickUpdates.selection = true - return this + TickUpdates.selection = true; + return this; } matchesSelection() { - let scope = this - let match = true + let scope = this; + let match = true; for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { - return false + return false; } } this.forEachChild(obj => { if (!obj.selected) { - match = false + match = false; } - }) - return match + }); + return match; } openUp() { - this.isOpen = true - this.updateElement() + this.isOpen = true; + this.updateElement(); if (this.parent && this.parent !== 'root') { - this.parent.openUp() + this.parent.openUp(); } - return this + return this; } getSaveCopy() { let copy = { @@ -101,11 +101,11 @@ export class Armature extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - } + }; for (let key in Armature.properties) { - Armature.properties[key].merge(copy, this) + Armature.properties[key].merge(copy, this); } - return copy + return copy; } getUndoCopy() { let copy = { @@ -114,32 +114,32 @@ export class Armature extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - } + }; for (let key in Armature.properties) { - Armature.properties[key].merge(copy, this) + Armature.properties[key].merge(copy, this); } - return copy + return copy; } getChildlessCopy(keep_uuid?: boolean) { - let base_armature = new Armature({ name: this.name }, keep_uuid ? this.uuid : null) + let base_armature = new Armature({ name: this.name }, keep_uuid ? this.uuid : null); for (let key in Armature.properties) { - Armature.properties[key].copy(this, base_armature) + Armature.properties[key].copy(this, base_armature); } - base_armature.name = this.name - base_armature.locked = this.locked - base_armature.visibility = this.visibility - base_armature.export = this.export - base_armature.isOpen = this.isOpen - return base_armature + base_armature.name = this.name; + base_armature.locked = this.locked; + base_armature.visibility = this.visibility; + base_armature.export = this.export; + base_armature.isOpen = this.isOpen; + return base_armature; } forEachChild( cb: (element: OutlinerElement) => void, type?: typeof OutlinerNode, forSelf?: boolean ) { - let i = 0 + let i = 0; if (forSelf) { - cb(this) + cb(this); } while (i < this.children.length) { if ( @@ -149,25 +149,25 @@ export class Armature extends OutlinerElement { : this.children[i] instanceof type) ) { // @ts-ignore - cb(this.children[i]) + cb(this.children[i]); } if (this.children[i].type === 'armature_bone') { - this.children[i].forEachChild(cb, type) + this.children[i].forEachChild(cb, type); } - i++ + i++; } } getAllBones() { - let bones = [] + let bones = []; function addBones(array: ArmatureBone[]) { for (let item of array) { - if (item instanceof ArmatureBone == false) continue - bones.push(item) - addBones(item.children) + if (item instanceof ArmatureBone == false) continue; + bones.push(item); + addBones(item.children); } } - addBones(this.children) - return bones + addBones(this.children); + return bones; } static behavior = { unique_name: false, @@ -176,13 +176,13 @@ export class Armature extends OutlinerElement { parent: true, child_types: ['armature_bone', 'mesh'], hide_in_screenshot: true, - } + }; - public title = tl('data.armature') - public type = 'armature' - public icon = 'accessibility' - public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false) - public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility] + public title = tl('data.armature'); + public type = 'armature'; + public icon = 'accessibility'; + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false); + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility]; public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, @@ -190,50 +190,50 @@ export class Armature extends OutlinerElement { new MenuSeparator('manage'), 'rename', 'delete', - ]) + ]); - static all: Armature[] - static selected: Armature[] + static all: Armature[]; + static selected: Armature[]; } -OutlinerElement.registerType(Armature, 'armature') +OutlinerElement.registerType(Armature, 'armature'); new NodePreviewController(Armature, { setup(element: Armature) { let object_3d = new THREE.Object3D() as { - isElement: boolean - no_export: boolean - } & THREE.Object3D - object_3d.rotation.order = 'ZYX' - object_3d.uuid = element.uuid.toUpperCase() - object_3d.name = element.name - object_3d.isElement = true - Project.nodes_3d[element.uuid] = object_3d + isElement: boolean; + no_export: boolean; + } & THREE.Object3D; + object_3d.rotation.order = 'ZYX'; + object_3d.uuid = element.uuid.toUpperCase(); + object_3d.name = element.name; + object_3d.isElement = true; + Project.nodes_3d[element.uuid] = object_3d; - object_3d.no_export = true + object_3d.no_export = true; - this.updateTransform(element) + this.updateTransform(element); - this.dispatchEvent('setup', { element }) + this.dispatchEvent('setup', { element }); }, updateTransform(element: Armature) { - let mesh = element.mesh + let mesh = element.mesh; if ( Format.bone_rig && element.parent instanceof OutlinerNode && element.parent.scene_object ) { - element.parent.scene_object.add(mesh) + element.parent.scene_object.add(mesh); } else if (mesh.parent !== Project.model_3d) { - Project.model_3d.add(mesh) + Project.model_3d.add(mesh); } - mesh.updateMatrixWorld() + mesh.updateMatrixWorld(); - this.dispatchEvent('update_transform', { element }) + this.dispatchEvent('update_transform', { element }); }, -}) +}); BARS.defineActions(function () { new Action('add_armature', { @@ -241,38 +241,38 @@ BARS.defineActions(function () { category: 'edit', condition: () => Modes.edit && Project.format?.armature_rig, click: function () { - Undo.initEdit({ outliner: true, elements: [] }) - let add_to_node = Outliner.selected[0] || Group.first_selected + Undo.initEdit({ outliner: true, elements: [] }); + let add_to_node = Outliner.selected[0] || Group.first_selected; if (!add_to_node && selected.length) { - add_to_node = selected.last() + add_to_node = selected.last(); } - let armature = new Armature() - armature.addTo(add_to_node) - armature.isOpen = true - armature.createUniqueName() - armature.init().select() + let armature = new Armature(); + armature.addTo(add_to_node); + armature.isOpen = true; + armature.createUniqueName(); + armature.init().select(); if (add_to_node instanceof Mesh) { - add_to_node.addTo(armature) + add_to_node.addTo(armature); } - let bone = new ArmatureBone() - bone.addTo(armature).init() + let bone = new ArmatureBone(); + bone.addTo(armature).init(); // @ts-ignore - Undo.finishEdit('Add armature', { outliner: true, elements: [armature, bone] }) + Undo.finishEdit('Add armature', { outliner: true, elements: [armature, bone] }); Vue.nextTick(function () { - updateSelection() + updateSelection(); if (settings.create_rename.value) { - armature.rename() + armature.rename(); } - armature.showInOutliner() - Blockbench.dispatchEvent('add_armature', { object: armature }) - }) + armature.showInOutliner(); + Blockbench.dispatchEvent('add_armature', { object: armature }); + }); }, - }) -}) + }); +}); Object.assign(window, { Armature, -}) +}); diff --git a/js/outliner/armature_bone.ts b/js/outliner/armature_bone.ts index 9e5348765..e8ca007c4 100644 --- a/js/outliner/armature_bone.ts +++ b/js/outliner/armature_bone.ts @@ -1,184 +1,184 @@ -import { Animation } from '../animations/animation' -import { Blockbench } from '../api' -import { THREE } from '../lib/libs' -import { flipNameOnAxis } from '../modeling/transform' -import { Armature } from './armature' -import { Vue } from '../lib/libs' +import { Animation } from '../animations/animation'; +import { Blockbench } from '../api'; +import { THREE } from '../lib/libs'; +import { flipNameOnAxis } from '../modeling/transform'; +import { Armature } from './armature'; +import { Vue } from '../lib/libs'; interface ArmatureBoneOptions { - name?: string - export?: boolean - locked?: boolean - visibility?: boolean - origin?: ArrayVector3 - rotation?: ArrayVector3 - vertex_weights?: Record - length?: number - width?: number - connected?: boolean - color?: number + name?: string; + export?: boolean; + locked?: boolean; + visibility?: boolean; + origin?: ArrayVector3; + rotation?: ArrayVector3; + vertex_weights?: Record; + length?: number; + width?: number; + connected?: boolean; + color?: number; } export class ArmatureBone extends OutlinerElement { - children: ArmatureBone[] - isOpen: boolean - visibility: boolean - origin: ArrayVector3 - rotation: ArrayVector3 - vertex_weights: Record - length: number - width: number - connected: boolean - color: number - old_size?: number + children: ArmatureBone[]; + isOpen: boolean; + visibility: boolean; + origin: ArrayVector3; + rotation: ArrayVector3; + vertex_weights: Record; + length: number; + width: number; + connected: boolean; + color: number; + old_size?: number; - static preview_controller: NodePreviewController + static preview_controller: NodePreviewController; constructor(data?: ArmatureBoneOptions, uuid?: UUID) { - super(data, uuid) + super(data, uuid); for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].reset(this) + ArmatureBone.properties[key].reset(this); } - this.name = 'bone' - this.children = [] - this.selected = false - this.locked = false - this.export = true - this.parent = 'root' - this.isOpen = false - this.visibility = true - this.vertex_weights = {} - this.color = Math.floor(Math.random() * markerColors.length) + this.name = 'bone'; + this.children = []; + this.selected = false; + this.locked = false; + this.export = true; + this.parent = 'root'; + this.isOpen = false; + this.visibility = true; + this.vertex_weights = {}; + this.color = Math.floor(Math.random() * markerColors.length); if (typeof data === 'object') { - this.extend(data) + this.extend(data); } else if (typeof data === 'string') { - this.name = data + this.name = data; } } get position() { - return this.origin + return this.origin; } extend(object: ArmatureBoneOptions) { for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(this, object) + ArmatureBone.properties[key].merge(this, object); } - Merge.string(this, object, 'name') - this.sanitizeName() - Merge.boolean(this, object, 'export') - Merge.boolean(this, object, 'locked') - Merge.boolean(this, object, 'visibility') - return this + Merge.string(this, object, 'name'); + this.sanitizeName(); + Merge.boolean(this, object, 'export'); + Merge.boolean(this, object, 'locked'); + Merge.boolean(this, object, 'visibility'); + return this; } getArmature(): Armature { - let parent = this.parent + let parent = this.parent; while (parent instanceof Armature == false && parent instanceof OutlinerNode) { - parent = parent.parent + parent = parent.parent; } - return parent as Armature + return parent as Armature; } init(): this { - super.init() + super.init(); if (!this.mesh || !this.mesh.parent) { - this.preview_controller.setup(this) + this.preview_controller.setup(this); } - Canvas.updateAllBones([this]) - return this + Canvas.updateAllBones([this]); + return this; } select(event?: Event, isOutlinerClick?: boolean): this { - super.select(event, isOutlinerClick) + super.select(event, isOutlinerClick); if (Animator.open && Animation.selected) { - Animation.selected.getBoneAnimator(this).select(true) + Animation.selected.getBoneAnimator(this).select(true); } - return this + return this; } markAsSelected(descendants: boolean): this { - Outliner.selected.safePush(this) - this.selected = true + Outliner.selected.safePush(this); + this.selected = true; if (descendants) { - this.children.forEach(child => child.markAsSelected(true)) + this.children.forEach(child => child.markAsSelected(true)); } - TickUpdates.selection = true - return this + TickUpdates.selection = true; + return this; } matchesSelection() { - let scope = this - let match = true + let scope = this; + let match = true; for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { - return false + return false; } } this.forEachChild(obj => { if (!obj.selected) { - match = false + match = false; } - }) - return match + }); + return match; } openUp() { - this.isOpen = true - this.updateElement() + this.isOpen = true; + this.updateElement(); if (this.parent && this.parent !== 'root') { - this.parent.openUp() + this.parent.openUp(); } - return this + return this; } transferOrigin(origin: ArrayVector3) { - if (!this.mesh) return - let q = new THREE.Quaternion().copy(this.mesh.quaternion) + if (!this.mesh) return; + let q = new THREE.Quaternion().copy(this.mesh.quaternion); let shift = new THREE.Vector3( this.origin[0] - origin[0], this.origin[1] - origin[1], this.origin[2] - origin[2] - ) - let dq = new THREE.Vector3().copy(shift) - dq.applyQuaternion(q) - shift.sub(dq) - shift.applyQuaternion(q.invert()) - this.origin.V3_set(origin) + ); + let dq = new THREE.Vector3().copy(shift); + dq.applyQuaternion(q); + shift.sub(dq); + shift.applyQuaternion(q.invert()); + this.origin.V3_set(origin); function iterateChild(obj: ArmatureBone) { if (obj instanceof ArmatureBone) { - obj.origin.V3_add(shift) - obj.children.forEach(child => iterateChild(child)) + obj.origin.V3_add(shift); + obj.children.forEach(child => iterateChild(child)); } } - this.children.forEach(child => iterateChild(child)) + this.children.forEach(child => iterateChild(child)); - Canvas.updatePositions() - return this + Canvas.updatePositions(); + return this; } getWorldCenter(): THREE.Vector3 { - let pos = new THREE.Vector3() - this.mesh.localToWorld(pos) - return pos + let pos = new THREE.Vector3(); + this.mesh.localToWorld(pos); + return pos; } flip(axis: number, center: number): this { - var offset = this.position[axis] - center - this.position[axis] = center - offset + var offset = this.position[axis] - center; + this.position[axis] = center - offset; this.rotation.forEach((n, i) => { - if (i != axis) this.rotation[i] = -n - }) + if (i != axis) this.rotation[i] = -n; + }); // Name - flipNameOnAxis(this, axis) + flipNameOnAxis(this, axis); - this.createUniqueName() - this.preview_controller.updateTransform(this) - return this + this.createUniqueName(); + this.preview_controller.updateTransform(this); + return this; } - size(): ArrayVector3 - size(axis: axisLetter): number + size(): ArrayVector3; + size(axis: axisLetter): number; size(axis?: axisLetter): number | ArrayVector3 { if (typeof axis == 'number') { - return axis == 1 ? this.length : this.width + return axis == 1 ? this.length : this.width; } - return [this.width, this.length, this.width] + return [this.width, this.length, this.width]; } getSize(axis) { - return this.size(axis) + return this.size(axis); } resize( move_value: number | ((input: number) => number), @@ -186,32 +186,32 @@ export class ArmatureBone extends OutlinerElement { invert?: boolean ) { if (axis_number == 1) { - let previous_length = this.old_size ?? this.length + let previous_length = this.old_size ?? this.length; if (typeof move_value == 'function') { - this.length = move_value(previous_length) + this.length = move_value(previous_length); } else { - this.length = previous_length + move_value * (invert ? -1 : 1) + this.length = previous_length + move_value * (invert ? -1 : 1); } } else { - let previous_width = this.old_size ?? this.width + let previous_width = this.old_size ?? this.width; if (typeof move_value == 'function') { - this.width = move_value(previous_width) + this.width = move_value(previous_width); } else { - this.width = previous_width + move_value * (invert ? -1 : 1) + this.width = previous_width + move_value * (invert ? -1 : 1); } } - this.preview_controller.updateTransform(this) + this.preview_controller.updateTransform(this); } setColor(index) { - this.color = index - this.preview_controller.updateFaces(this) - let armature = this.getArmature() + this.color = index; + this.preview_controller.updateFaces(this); + let armature = this.getArmature(); // Update vertex colors Canvas.updateView({ elements: Mesh.all.filter(mesh => armature && mesh.getArmature() == armature), element_aspects: { geometry: true }, - }) - return this + }); + return this; } getSaveCopy(project) { let copy = { @@ -220,11 +220,11 @@ export class ArmatureBone extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - } + }; for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(copy, this) + ArmatureBone.properties[key].merge(copy, this); } - return copy + return copy; } getUndoCopy() { let copy = { @@ -233,30 +233,30 @@ export class ArmatureBone extends OutlinerElement { type: this.type, name: this.name, children: this.children.map(c => c.uuid), - } + }; for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(copy, this) + ArmatureBone.properties[key].merge(copy, this); } - return copy + return copy; } getChildlessCopy(keep_uuid: boolean = false) { - let base_bone = new ArmatureBone({ name: this.name }, keep_uuid ? this.uuid : null) + let base_bone = new ArmatureBone({ name: this.name }, keep_uuid ? this.uuid : null); for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].copy(this, base_bone) + ArmatureBone.properties[key].copy(this, base_bone); } - base_bone.name = this.name - base_bone.origin.V3_set(this.origin) - base_bone.rotation.V3_set(this.rotation) - base_bone.locked = this.locked - base_bone.visibility = this.visibility - base_bone.export = this.export - base_bone.isOpen = this.isOpen - return base_bone + base_bone.name = this.name; + base_bone.origin.V3_set(this.origin); + base_bone.rotation.V3_set(this.rotation); + base_bone.locked = this.locked; + base_bone.visibility = this.visibility; + base_bone.export = this.export; + base_bone.isOpen = this.isOpen; + return base_bone; } forEachChild(cb: (element: ArmatureBone) => void, type?: any, forSelf?: boolean) { - let i = 0 + let i = 0; if (forSelf) { - cb(this) + cb(this); } while (i < this.children.length) { if ( @@ -265,12 +265,12 @@ export class ArmatureBone extends OutlinerElement { ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type) ) { - cb(this.children[i]) + cb(this.children[i]); } if (this.children[i].type === 'armature_bone') { - this.children[i].forEachChild(cb, type) + this.children[i].forEachChild(cb, type); } - i++ + i++; } } static behavior = { @@ -284,15 +284,15 @@ export class ArmatureBone extends OutlinerElement { select_children: 'self_first', hide_in_screenshot: true, marker_color: true, - } - static all: ArmatureBone[] - static selected: ArmatureBone[] + }; + static all: ArmatureBone[]; + static selected: ArmatureBone[]; - public title = tl('data.armature_bone') - public type = 'armature_bone' - public icon = 'humerus' - public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false) - public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility] + public title = tl('data.armature_bone'); + public type = 'armature_bone'; + public icon = 'humerus'; + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false); + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility]; public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, @@ -303,51 +303,51 @@ export class ArmatureBone extends OutlinerElement { new MenuSeparator('manage'), 'rename', 'delete', - ]) + ]); } ArmatureBone.addBehaviorOverride({ condition: { features: ['bone_rig'] }, behavior: { unique_name: true, }, -}) +}); -OutlinerElement.registerType(ArmatureBone, 'armature_bone') +OutlinerElement.registerType(ArmatureBone, 'armature_bone'); -new Property(ArmatureBone, 'vector', 'origin', { default: [0, 0, 0] }) -new Property(ArmatureBone, 'vector', 'rotation') -new Property(ArmatureBone, 'number', 'length', { default: 8 }) -new Property(ArmatureBone, 'number', 'width', { default: 2 }) +new Property(ArmatureBone, 'vector', 'origin', { default: [0, 0, 0] }); +new Property(ArmatureBone, 'vector', 'rotation'); +new Property(ArmatureBone, 'number', 'length', { default: 8 }); +new Property(ArmatureBone, 'number', 'width', { default: 2 }); new Property(ArmatureBone, 'boolean', 'connected', { default: true, inputs: { element_panel: { input: { label: 'armature_bone.connected', type: 'checkbox' }, onChange() { - let parents = [] + let parents = []; ArmatureBone.selected.forEach(b => { - if (b.parent instanceof ArmatureBone) parents.safePush(b.parent) - }) - console.log(parents) - Canvas.updateView({ elements: parents, element_aspects: { transform: true } }) + if (b.parent instanceof ArmatureBone) parents.safePush(b.parent); + }); + console.log(parents); + Canvas.updateView({ elements: parents, element_aspects: { transform: true } }); }, }, }, -}) -new Property(ArmatureBone, 'number', 'color') -new Property(ArmatureBone, 'object', 'vertex_weights') +}); +new Property(ArmatureBone, 'number', 'color'); +new Property(ArmatureBone, 'object', 'vertex_weights'); type FakeObjectType = { - isElement: boolean - no_export: boolean - fix_position: THREE.Vector3 - fix_rotation: THREE.Euler - inverse_bind_matrix: THREE.Matrix4 -} + isElement: boolean; + no_export: boolean; + fix_position: THREE.Vector3; + fix_rotation: THREE.Euler; + inverse_bind_matrix: THREE.Matrix4; +}; type PreviewControllerType = NodePreviewController & { - material: THREE.MeshLambertMaterial - material_selected: THREE.MeshLambertMaterial -} + material: THREE.MeshLambertMaterial; + material_selected: THREE.MeshLambertMaterial; +}; new NodePreviewController(ArmatureBone, { material: new THREE.MeshLambertMaterial({ color: 0xc8c9cb, @@ -366,16 +366,16 @@ new NodePreviewController(ArmatureBone, { side: THREE.FrontSide, }), setup(element: ArmatureBone) { - let object_3d = new THREE.Bone() as FakeObjectType & THREE.Bone - object_3d.rotation.order = 'ZYX' - object_3d.uuid = element.uuid.toUpperCase() - object_3d.name = element.name + let object_3d = new THREE.Bone() as FakeObjectType & THREE.Bone; + object_3d.rotation.order = 'ZYX'; + object_3d.uuid = element.uuid.toUpperCase(); + object_3d.name = element.name; //object_3d.isElement = true; - Project.nodes_3d[element.uuid] = object_3d + Project.nodes_3d[element.uuid] = object_3d; - let geometry = new THREE.BufferGeometry() + let geometry = new THREE.BufferGeometry(); let r = 1, - m = 0.2 + m = 0.2; let vertices = [ 0, 0, @@ -449,132 +449,132 @@ new NodePreviewController(ArmatureBone, { -r, m, -r, - ] + ]; let normals = [ 0.0, -0.5, 0.4, 0.0, -0.5, -0.4, 0.4, -0.5, 0.0, -0.4, -0.5, 0.0, 0.0, 0.2, 0.6, 0.0, 0.2, -0.6, 0.6, 0.2, 0.0, -0.6, 0.2, 0.0, - ] - let normals_array = [] + ]; + let normals_array = []; for (let i = 0; i < normals.length; i += 3) { for (let j = 0; j < 3; j++) { - normals_array.push(normals[i], normals[i + 1], normals[i + 2]) + normals_array.push(normals[i], normals[i + 1], normals[i + 2]); } } - geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)) - geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals_array, 3)) - geometry.normalizeNormals() - geometry.computeBoundingBox() - geometry.computeBoundingSphere() - let material = (ArmatureBone.preview_controller as PreviewControllerType).material + geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); + geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals_array, 3)); + geometry.normalizeNormals(); + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + let material = (ArmatureBone.preview_controller as PreviewControllerType).material; let mesh: { no_export?: boolean; isElement?: true; type?: string } & THREE.Mesh = - new THREE.Mesh(geometry, material) - mesh.renderOrder = 20 - mesh.visible = element.visibility - mesh.no_export = true - mesh.name = element.uuid - mesh.type = element.type - mesh.isElement = true - object_3d.add(mesh) + new THREE.Mesh(geometry, material); + mesh.renderOrder = 20; + mesh.visible = element.visibility; + mesh.no_export = true; + mesh.name = element.uuid; + mesh.type = element.type; + mesh.isElement = true; + object_3d.add(mesh); - object_3d.no_export = true - object_3d.fix_position = new THREE.Vector3() - object_3d.fix_rotation = new THREE.Euler() - object_3d.inverse_bind_matrix = new THREE.Matrix4() + object_3d.no_export = true; + object_3d.fix_position = new THREE.Vector3(); + object_3d.fix_rotation = new THREE.Euler(); + object_3d.inverse_bind_matrix = new THREE.Matrix4(); - this.updateTransform(element) - this.updateFaces(element) - this.updateSelection(element) + this.updateTransform(element); + this.updateFaces(element); + this.updateSelection(element); - this.dispatchEvent('setup', { element }) + this.dispatchEvent('setup', { element }); }, updateFaces(element: ArmatureBone) { - let color_material = Canvas.coloredSolidMaterials[element.color % markerColors.length] - let color_value = color_material.uniforms.base.value - let color_array = [] + let color_material = Canvas.coloredSolidMaterials[element.color % markerColors.length]; + let color_value = color_material.uniforms.base.value; + let color_array = []; for (let i = 0; i < 24; i++) { - color_array.push(color_value.r, color_value.g, color_value.b) + color_array.push(color_value.r, color_value.g, color_value.b); } - ;(element.mesh.children[0] as THREE.Mesh).geometry.setAttribute( + (element.mesh.children[0] as THREE.Mesh).geometry.setAttribute( 'color', new THREE.Float32BufferAttribute(color_array, 3) - ) + ); }, updateTransform(element: ArmatureBone) { - let bone = element.scene_object as FakeObjectType & THREE.Bone + let bone = element.scene_object as FakeObjectType & THREE.Bone; - bone.rotation.order = 'ZYX' + bone.rotation.order = 'ZYX'; // @ts-expect-error - bone.rotation.setFromDegreeArray(element.rotation) - bone.position.fromArray(element.origin) - bone.scale.x = bone.scale.y = bone.scale.z = 1 + bone.rotation.setFromDegreeArray(element.rotation); + bone.position.fromArray(element.origin); + bone.scale.x = bone.scale.y = bone.scale.z = 1; if (element.parent instanceof OutlinerNode) { - let parent_bone = element.parent.scene_object - parent_bone.add(bone) + let parent_bone = element.parent.scene_object; + parent_bone.add(bone); if (element.parent instanceof ArmatureBone) { - ArmatureBone.preview_controller.updateTransform(element.parent) + ArmatureBone.preview_controller.updateTransform(element.parent); } } else if (bone.parent) { - bone.parent.remove(bone) + bone.parent.remove(bone); } - let connected_children = element.children.filter(b => b.connected) + let connected_children = element.children.filter(b => b.connected); if (connected_children.length >= 2) { - let box = new THREE.Box3() + let box = new THREE.Box3(); for (let bone of connected_children) { - box.expandByPoint(Reusable.vec1.fromArray(bone.position)) + box.expandByPoint(Reusable.vec1.fromArray(bone.position)); } - let tail_offset = box.getCenter(Reusable.vec1) - bone.children[0].scale.y = Math.max(2, tail_offset.length()) + let tail_offset = box.getCenter(Reusable.vec1); + bone.children[0].scale.y = Math.max(2, tail_offset.length()); bone.children[0].quaternion.setFromUnitVectors( new THREE.Vector3(0, 1, 0), tail_offset.normalize() - ) + ); } else if (connected_children.length == 1) { - let tail_offset = Reusable.vec1.fromArray(connected_children[0].position) - bone.children[0].scale.y = tail_offset.length() + let tail_offset = Reusable.vec1.fromArray(connected_children[0].position); + bone.children[0].scale.y = tail_offset.length(); bone.children[0].quaternion.setFromUnitVectors( new THREE.Vector3(0, 1, 0), tail_offset.normalize() - ) + ); } else { - bone.children[0].rotation.set(0, 0, 0) - bone.children[0].scale.x = element.width / 2 - bone.children[0].scale.z = element.width / 2 - bone.children[0].scale.y = element.length + bone.children[0].rotation.set(0, 0, 0); + bone.children[0].scale.x = element.width / 2; + bone.children[0].scale.z = element.width / 2; + bone.children[0].scale.y = element.length; } - bone.fix_position.copy(bone.position) - bone.fix_rotation.copy(bone.rotation) - bone.inverse_bind_matrix.copy(bone.matrixWorld).invert() + bone.fix_position.copy(bone.position); + bone.fix_rotation.copy(bone.rotation); + bone.inverse_bind_matrix.copy(bone.matrixWorld).invert(); /*for (let child of element.children) { if (child.scene_object) this.updateTransform(child); }*/ - bone.updateMatrixWorld() + bone.updateMatrixWorld(); - this.dispatchEvent('update_transform', { element }) + this.dispatchEvent('update_transform', { element }); }, updateSelection(element: ArmatureBone) { - let material = element.selected ? this.material_selected : this.material - let preview_mesh = element.scene_object.children[0] as THREE.Mesh - preview_mesh.material = material + let material = element.selected ? this.material_selected : this.material; + let preview_mesh = element.scene_object.children[0] as THREE.Mesh; + preview_mesh.material = material; }, -}) +}); export function getAllArmatureBones() { - let ta = [] + let ta = []; function iterate(array) { for (let obj of array) { if (obj instanceof ArmatureBone) { - ta.push(obj) - iterate(obj.children) + ta.push(obj); + iterate(obj.children); } } } - iterate(Outliner.root) - return ta + iterate(Outliner.root); + return ta; } BARS.defineActions(function () { @@ -584,38 +584,38 @@ BARS.defineActions(function () { keybind: new Keybind({ key: 'e', shift: true }), condition: () => Modes.edit && (ArmatureBone.selected[0] || Armature.selected[0]), click: function () { - Undo.initEdit({ outliner: true, elements: [] }) - let add_to_node = Outliner.selected[0] || Group.first_selected + Undo.initEdit({ outliner: true, elements: [] }); + let add_to_node = Outliner.selected[0] || Group.first_selected; if (!add_to_node && selected.length) { - add_to_node = selected.last() + add_to_node = selected.last(); } let new_instance = new ArmatureBone({ origin: add_to_node instanceof ArmatureBone ? [0, add_to_node.length ?? 8, 0] : undefined, - }) - new_instance.addTo(add_to_node) - new_instance.isOpen = true + }); + new_instance.addTo(add_to_node); + new_instance.isOpen = true; if (Format.bone_rig) { - new_instance.createUniqueName() + new_instance.createUniqueName(); } - new_instance.init().select() - Undo.finishEdit('Add armature bone', { outliner: true, elements: [new_instance] }) + new_instance.init().select(); + Undo.finishEdit('Add armature bone', { outliner: true, elements: [new_instance] }); Vue.nextTick(function () { - updateSelection() + updateSelection(); if (settings.create_rename.value) { - new_instance.rename() + new_instance.rename(); } - new_instance.showInOutliner() - Blockbench.dispatchEvent('add_armature_bone', { object: new_instance }) - }) + new_instance.showInOutliner(); + Blockbench.dispatchEvent('add_armature_bone', { object: new_instance }); + }); }, - }) -}) + }); +}); Object.assign(window, { ArmatureBone, getAllArmatureBones, -}) +}); diff --git a/js/outliner/collections.ts b/js/outliner/collections.ts index 0188a280c..7b432e657 100644 --- a/js/outliner/collections.ts +++ b/js/outliner/collections.ts @@ -1,125 +1,127 @@ -import { Animation } from '../animations/animation' -import { SharedActions } from '../interface/shared_actions' -import { Prop } from '../misc' -import { guid } from '../util/math_util' -import { Property } from '../util/property' -import { OutlinerElement, OutlinerNode } from './outliner' -import { Toolbar } from '../interface/toolbars' -import { Group } from './group' -import { Interface } from '../interface/interface' -import { Menu } from '../interface/menu' -import { Blockbench } from '../api' -import { removeEventListeners } from '../util/util' -import { Action } from '../interface/actions' -import { Clipbench } from '../copy_paste' -import { getFocusedTextInput } from '../interface/keyboard' -import { tl } from '../languages' -import { Panel } from '../interface/panels' -import { Codecs } from '../io/codec' +import { Animation } from '../animations/animation'; +import { SharedActions } from '../interface/shared_actions'; +import { Prop } from '../misc'; +import { guid } from '../util/math_util'; +import { Property } from '../util/property'; +import { OutlinerElement, OutlinerNode } from './outliner'; +import { Toolbar } from '../interface/toolbars'; +import { Group } from './group'; +import { Interface } from '../interface/interface'; +import { Menu } from '../interface/menu'; +import { Blockbench } from '../api'; +import { removeEventListeners } from '../util/util'; +import { Action } from '../interface/actions'; +import { Clipbench } from '../copy_paste'; +import { getFocusedTextInput } from '../interface/keyboard'; +import { tl } from '../languages'; +import { Panel } from '../interface/panels'; +import { Codecs } from '../io/codec'; interface CollectionOptions { - children?: string[] - name?: string - export_codec?: string - export_path?: string - visibility?: boolean + children?: string[]; + name?: string; + export_codec?: string; + export_path?: string; + visibility?: boolean; } /** * Collections are "selection presets" for a set of groups and elements in your project, independent from outliner hierarchy */ export class Collection { - uuid: string - name: string - selected: boolean + uuid: string; + name: string; + selected: boolean; /** * List of direct children, referenced by UUIDs */ - children: string[] - export_path: string - codec: string - menu: Menu - export_codec: string - visibility: boolean + children: string[]; + export_path: string; + codec: string; + menu: Menu; + export_codec: string; + visibility: boolean; - static properties: Record> + static properties: Record>; /** * Get all collections */ - static all: Collection[] + static all: Collection[]; /** * Get selected collections */ - static selected: Collection[] + static selected: Collection[]; constructor(data: CollectionOptions, uuid?: string) { - this.uuid = uuid && isUUID(uuid) ? uuid : guid() - this.selected = false - this.children = [] + this.uuid = uuid && isUUID(uuid) ? uuid : guid(); + this.selected = false; + this.children = []; for (let key in Collection.properties) { - Collection.properties[key].reset(this) + Collection.properties[key].reset(this); } - if (data) this.extend(data) + if (data) this.extend(data); } extend(data: CollectionOptions): this { for (var key in Collection.properties) { - Collection.properties[key].merge(this, data) + Collection.properties[key].merge(this, data); } - return this + return this; } select(event?: KeyboardEvent | MouseEvent): this { - this.selected = true + this.selected = true; if ( (!(event?.shiftKey || Pressing.overrides.shift) && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) || Modes.animate ) { - unselectAllElements() - Collection.all.forEach(c => (c.selected = false)) + unselectAllElements(); + Collection.all.forEach(c => (c.selected = false)); } - this.selected = true - let i = 0 + this.selected = true; + let i = 0; if (Modes.animate && Animation.selected && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) { - Timeline.animators.empty() + Timeline.animators.empty(); } for (let node of this.getChildren()) { if (Modes.animate && Animation.selected) { // @ts-ignore if (node.constructor.animator) { - let animator = Animation.selected.getBoneAnimator(node) + let animator = Animation.selected.getBoneAnimator(node); if (animator) { - animator.addToTimeline(true) + animator.addToTimeline(true); } if (i == 0) { - node.select() + node.select(); } } } else { if (node instanceof Group) { - node.multiSelect() + node.multiSelect(); } else { - Outliner.selected.safePush(node) + Outliner.selected.safePush(node); } } - i++ + i++; } - updateSelection() - return this + updateSelection(); + return this; } clickSelect(event) { - Undo.initSelection({ collections: true, timeline: Modes.animate }) - this.select(event) - Undo.finishSelection('Select collection') + Undo.initSelection({ collections: true, timeline: Modes.animate }); + this.select(event); + Undo.finishSelection('Select collection'); } /** * Get all direct children */ getChildren(): OutlinerNode[] { - return this.children.map(uuid => OutlinerNode.uuids[uuid]).filter(node => node != undefined) + return this.children + .map(uuid => OutlinerNode.uuids[uuid]) + .filter(node => node != undefined); } add(): this { - Collection.all.safePush(this) - return this + Collection.all.safePush(this); + return this; } /** * Adds the current outliner selection to this collection @@ -127,103 +129,103 @@ export class Collection { addSelection(): this { if (Group.multi_selected.length) { for (let group of Group.multi_selected) { - this.children.safePush(group.uuid) + this.children.safePush(group.uuid); } } for (let element of Outliner.selected) { if (!(element instanceof OutlinerNode && element.parent.selected)) { - this.children.safePush(element.uuid) + this.children.safePush(element.uuid); } } - return this + return this; } /** * Returns the visibility of the first contained node that supports visibility. Otherwise returns true. */ getVisibility(): boolean { let match = this.getChildren().find(node => { - return node && 'visibility' in node && typeof node.visibility == 'boolean' - }) + return node && 'visibility' in node && typeof node.visibility == 'boolean'; + }); // @ts-ignore - return match ? match.visibility : true + return match ? match.visibility : true; } /** * Get all children, including indirect ones */ getAllChildren(): OutlinerNode[] { - let children = this.getChildren() - let nodes = [] + let children = this.getChildren(); + let nodes = []; for (let child of children) { - nodes.safePush(child) + nodes.safePush(child); if ('forEachChild' in child && typeof child.forEachChild == 'function') { - child.forEachChild(subchild => nodes.safePush(subchild)) + child.forEachChild(subchild => nodes.safePush(subchild)); } } - return nodes + return nodes; } /** * Toggle visibility of everything in the collection * @param event If the alt key is pressed, the result is inverted and the visibility of everything but the collection will be toggled */ toggleVisibility(event: KeyboardEvent | MouseEvent): void { - let children = this.getChildren() - if (!children.length) return - let groups = [] - let elements = [] + let children = this.getChildren(); + if (!children.length) return; + let groups = []; + let elements = []; function update(node: OutlinerNode) { - if ('visibility' in node == false || typeof node.visibility != 'boolean') return + if ('visibility' in node == false || typeof node.visibility != 'boolean') return; if (node instanceof Group) { - groups.push(node) + groups.push(node); } else { - elements.push(node) + elements.push(node); } } for (let child of children) { - update(child) + update(child); if ('forEachChild' in child && typeof child.forEachChild == 'function') { - child.forEachChild(update) + child.forEachChild(update); } } if (event.altKey) { // invert selection - elements = Outliner.elements.filter(e => !elements.includes(e)) - groups = Group.all.filter(e => !groups.includes(e)) + elements = Outliner.elements.filter(e => !elements.includes(e)); + groups = Group.all.filter(e => !groups.includes(e)); } - let all = groups.concat(elements) - let state = all[0]?.visibility != true - Undo.initEdit({ groups, elements }) + let all = groups.concat(elements); + let state = all[0]?.visibility != true; + Undo.initEdit({ groups, elements }); all.forEach(node => { - node.visibility = state - }) - Canvas.updateView({ elements, element_aspects: { visibility: true } }) - Undo.finishEdit('Toggle collection visibility') + node.visibility = state; + }); + Canvas.updateView({ elements, element_aspects: { visibility: true } }); + Undo.finishEdit('Toggle collection visibility'); } /** * Opens the context menu */ showContextMenu(event) { - if (!this.selected) this.clickSelect(event) - this.menu.open(event, this) - return this + if (!this.selected) this.clickSelect(event); + this.menu.open(event, this); + return this; } getUndoCopy() { let copy = { uuid: this.uuid, index: Collection.all.indexOf(this), - } + }; for (var key in Collection.properties) { - Collection.properties[key].copy(this, copy) + Collection.properties[key].copy(this, copy); } - return copy + return copy; } getSaveCopy() { let copy = { uuid: this.uuid, - } + }; for (var key in Collection.properties) { - Collection.properties[key].copy(this, copy) + Collection.properties[key].copy(this, copy); } - return copy + return copy; } /** * Opens the properties dialog @@ -236,18 +238,18 @@ export class Collection { * Export Format * Offset */ - let collection = this + let collection = this; function getContentList() { let types = { group: [], - } + }; for (let child of collection.getChildren()) { // @ts-ignore - let type = child.type - if (!types[type]) types[type] = [] - types[type].push(child) + let type = child.type; + if (!types[type]) types[type] = []; + types[type].push(child); } - let list = [] + let list = []; for (let key in types) { for (let node of types[key]) { list.push({ @@ -257,19 +259,19 @@ export class Collection { key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon, - }) + }); } } - return list + return list; } type PropertiesComponentData = { content: { - name: string - uuid: string - icon: string - }[] - selected: string[] - } + name: string; + uuid: string; + icon: string; + }[]; + selected: string[]; + }; let dialog = new Dialog({ id: 'collection_properties', title: this.name, @@ -278,7 +280,7 @@ export class Collection { delete: { keybind: new Keybind({ key: 46 }), run() { - this.content_vue.remove() + this.content_vue.remove(); }, }, }, @@ -303,17 +305,17 @@ export class Collection { methods: { selectAll(this: PropertiesComponentData) { for (let node of this.content) { - this.selected.safePush(node.uuid) + this.selected.safePush(node.uuid); } }, selectNone(this: PropertiesComponentData) { - this.selected.empty() + this.selected.empty(); }, remove(this: PropertiesComponentData) { for (let uuid of this.selected) { - this.content.remove(this.content.find(node => node.uuid == uuid)) + this.content.remove(this.content.find(node => node.uuid == uuid)); } - this.selected.empty() + this.selected.empty(); }, addWithFilter(this: PropertiesComponentData, event) { // @ts-ignore @@ -325,11 +327,11 @@ export class Collection { uuid: node.uuid, name: node.name, icon: node.icon, - }) + }); } } }, - }) + }); }, }, template: `
    @@ -348,10 +350,10 @@ export class Collection {
    `, }, onFormChange(form) { - this.component.data.loop_mode = form.loop + this.component.data.loop_mode = form.loop; }, onConfirm: form_data => { - let vue_data = dialog.content_vue.$data as PropertiesComponentData + let vue_data = dialog.content_vue.$data as PropertiesComponentData; if ( form_data.name != this.name || form_data.export_path != this.export_path || @@ -360,26 +362,26 @@ export class Collection { uuid => !vue_data.content.find(node => node.uuid == uuid) ) ) { - Undo.initEdit({ collections: [this] }) + Undo.initEdit({ collections: [this] }); this.extend({ name: form_data.name, export_path: form_data.export_path, - }) - if (isApp) this.export_path = form_data.path - this.children.replace(vue_data.content.map(node => node.uuid)) + }); + if (isApp) this.export_path = form_data.path; + this.children.replace(vue_data.content.map(node => node.uuid)); - Blockbench.dispatchEvent('edit_collection_properties', { collection: this }) + Blockbench.dispatchEvent('edit_collection_properties', { collection: this }); - Undo.finishEdit('Edit collection properties') + Undo.finishEdit('Edit collection properties'); } - dialog.hide().delete() + dialog.hide().delete(); }, onCancel() { - dialog.hide().delete() + dialog.hide().delete(); }, - }) - dialog.show() + }); + dialog.show(); } } Collection.prototype.menu = new Menu([ @@ -393,22 +395,22 @@ Collection.prototype.menu = new Menu([ 'delete', new MenuSeparator('export'), collection => { - let codec = Codecs[collection.codec] + let codec = Codecs[collection.codec]; if ( codec?.export_action && collection.export_path && Condition(codec.export_action.condition) ) { - let export_action = codec.export_action + let export_action = codec.export_action; return { id: 'export_as', name: tl('menu.collection.export_as', pathToName(collection.export_path, true)), icon: export_action.icon, description: export_action.description, click() { - codec.writeCollection(collection) + codec.writeCollection(collection); }, - } + }; } }, { @@ -416,38 +418,38 @@ Collection.prototype.menu = new Menu([ name: 'generic.export', icon: 'insert_drive_file', children: collection => { - let actions = [] + let actions = []; for (let id in Codecs) { - let codec = Codecs[id] + let codec = Codecs[id]; if ( !codec.export_action || !codec.support_partial_export || !Condition(codec.export_action.condition) ) - continue + continue; - let export_action = codec.export_action + let export_action = codec.export_action; let new_action = { name: export_action.name, icon: export_action.icon, description: export_action.description, click() { - codec.exportCollection(collection) + codec.exportCollection(collection); }, - } + }; if (id == 'project') { new_action = { name: 'menu.collection.export_project', icon: 'icon-blockbench_file', description: '', click() { - codec.exportCollection(collection) + codec.exportCollection(collection); }, - } + }; } - actions.push(new_action) + actions.push(new_action); } - return actions + return actions; }, }, new MenuSeparator('properties'), @@ -455,79 +457,79 @@ Collection.prototype.menu = new Menu([ icon: 'list', name: 'menu.texture.properties', click(collection) { - collection.propertiesDialog() + collection.propertiesDialog(); }, }, -]) -new Property(Collection, 'string', 'name', { default: 'collection' }) -new Property(Collection, 'string', 'export_codec') -new Property(Collection, 'string', 'export_path') -new Property(Collection, 'array', 'children') -new Property(Collection, 'boolean', 'visibility', { default: false }) +]); +new Property(Collection, 'string', 'name', { default: 'collection' }); +new Property(Collection, 'string', 'export_codec'); +new Property(Collection, 'string', 'export_path'); +new Property(Collection, 'array', 'children'); +new Property(Collection, 'boolean', 'visibility', { default: false }); Object.defineProperty(Collection, 'all', { get() { // @ts-ignore - return Project.collections + return Project.collections; }, -}) +}); Object.defineProperty(Collection, 'selected', { get() { // @ts-ignore - return Project ? Project.collections.filter(c => c.selected) : [] + return Project ? Project.collections.filter(c => c.selected) : []; }, -}) +}); SharedActions.add('delete', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - let selected = Collection.selected.slice() - Undo.initEdit({ collections: selected }) + let selected = Collection.selected.slice(); + Undo.initEdit({ collections: selected }); for (let c of selected) { - Collection.all.remove(c) + Collection.all.remove(c); } - selected.empty() - Undo.finishEdit('Remove collection') + selected.empty(); + Undo.finishEdit('Remove collection'); }, -}) +}); SharedActions.add('duplicate', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - let new_collections = [] - Undo.initEdit({ collections: new_collections }) + let new_collections = []; + Undo.initEdit({ collections: new_collections }); for (let original of Collection.selected.slice()) { - let copy = new Collection(original) - copy.name += ' - copy' - copy.add().select() - new_collections.push(copy) + let copy = new Collection(original); + copy.name += ' - copy'; + copy.add().select(); + new_collections.push(copy); } - Undo.finishEdit('Duplicate collection') + Undo.finishEdit('Duplicate collection'); }, -}) +}); SharedActions.add('copy', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { - Clipbench.collections = Collection.selected.map(collection => collection.getUndoCopy()) + Clipbench.collections = Collection.selected.map(collection => collection.getUndoCopy()); }, -}) +}); SharedActions.add('paste', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Clipbench.collections?.length, run() { - let new_collections = [] - Undo.initEdit({ collections: new_collections }) + let new_collections = []; + Undo.initEdit({ collections: new_collections }); for (let data of Clipbench.collections) { - let copy = new Collection(data) - copy.name += ' - copy' - copy.add().select() - new_collections.push(copy) + let copy = new Collection(data); + copy.name += ' - copy'; + copy.add().select(); + new_collections.push(copy); } - Undo.finishEdit('Paste collection') + Undo.finishEdit('Paste collection'); }, -}) +}); BARS.defineActions(() => { new Action('create_collection', { @@ -536,67 +538,67 @@ BARS.defineActions(() => { keybind: new Keybind({ key: 'l', ctrl: true }), condition: { modes: ['edit', 'paint', 'animate'] }, click() { - Undo.initEdit({ collections: [] }) - let collection = new Collection({}) - collection.add().addSelection().select() - Undo.finishEdit('Create collection', { collections: [collection] }) - updateSelection() + Undo.initEdit({ collections: [] }); + let collection = new Collection({}); + collection.add().addSelection().select(); + Undo.finishEdit('Create collection', { collections: [collection] }); + updateSelection(); }, - }) + }); new Action('set_collection_content_to_selection', { icon: 'unarchive', category: 'select', condition: () => Collection.selected.length, click() { - let collections = Collection.selected - Undo.initEdit({ collections }) + let collections = Collection.selected; + Undo.initEdit({ collections }); for (let collection of collections) { - collection.children.empty() - collection.addSelection() + collection.children.empty(); + collection.addSelection(); } - Undo.finishEdit('Set collection content to selection') + Undo.finishEdit('Set collection content to selection'); }, - }) + }); new Action('add_to_collection', { icon: 'box_add', category: 'select', condition: () => Collection.selected.length, click() { - let collections = Collection.selected - Undo.initEdit({ collections }) + let collections = Collection.selected; + Undo.initEdit({ collections }); for (let collection of collections) { - collection.addSelection() + collection.addSelection(); } - Undo.finishEdit('Add selection to collection') + Undo.finishEdit('Add selection to collection'); }, - }) -}) + }); +}); Interface.definePanels(function () { function eventTargetToCollection(target: HTMLElement): [Collection?, HTMLElement?] { - let target_node: HTMLElement | undefined = target - let i = 0 + let target_node: HTMLElement | undefined = target; + let i = 0; while ( target_node && target_node.classList && !target_node.classList.contains('collection') ) { if (i < 3 && target_node) { - target_node = target_node.parentElement - i++ + target_node = target_node.parentElement; + i++; } else { - return [] + return []; } } - let uuid_value = target_node.getAttribute('uuid') as string - return [Collection.all.find(collection => collection.uuid == uuid_value), target_node] + let uuid_value = target_node.getAttribute('uuid') as string; + return [Collection.all.find(collection => collection.uuid == uuid_value), target_node]; } function getOrder(loc, obj) { if (!obj) { - return + return; } else { - if (loc <= 20) return -1 - return 1 + if (loc <= 20) return -1; + return 1; } } new Panel('collections', { @@ -622,142 +624,144 @@ Interface.definePanels(function () { data() { return { collections: [], - } + }; }, methods: { openMenu(event) { - Interface.Panels.collections.menu.show(event) + Interface.Panels.collections.menu.show(event); }, dragCollection(e1) { - if (getFocusedTextInput()) return - if (e1.button == 1 || e1.button == 2) return - convertTouchEvent(e1) + if (getFocusedTextInput()) return; + if (e1.button == 1 || e1.button == 2) return; + convertTouchEvent(e1); - let [collection] = eventTargetToCollection(e1.target) - if (!collection) return - let active = false - let helper: HTMLDivElement - let timeout: NodeJS.Timeout | null = null - let drop_target, drop_target_node, order - let last_event = e1 + let [collection] = eventTargetToCollection(e1.target); + if (!collection) return; + let active = false; + let helper: HTMLDivElement; + let timeout: NodeJS.Timeout | null = null; + let drop_target, drop_target_node, order; + let last_event = e1; function move(e2) { - convertTouchEvent(e2) - let offset = [e2.clientX - e1.clientX, e2.clientY - e1.clientY] + convertTouchEvent(e2); + let offset = [e2.clientX - e1.clientX, e2.clientY - e1.clientY]; if (!active) { let distance = Math.sqrt( Math.pow(offset[0], 2) + Math.pow(offset[1], 2) - ) + ); if (Blockbench.isTouch) { if (distance > 20 && timeout) { - clearTimeout(timeout) - timeout = null + clearTimeout(timeout); + timeout = null; } else { document.getElementById('collections_list').scrollTop += - last_event.clientY - e2.clientY + last_event.clientY - e2.clientY; } } else if (distance > 6) { - active = true + active = true; } } else { - if (e2) e2.preventDefault() + if (e2) e2.preventDefault(); - if (Menu.open) Menu.open.hide() + if (Menu.open) Menu.open.hide(); if (!helper) { - helper = document.createElement('div') - helper.id = 'animation_drag_helper' - let icon = Blockbench.getIconNode('inventory_2') - helper.append(icon) - let span = document.createElement('span') - span.innerText = collection.name - helper.append(span) - document.body.append(helper) - Blockbench.addFlag('dragging_collections') + helper = document.createElement('div'); + helper.id = 'animation_drag_helper'; + let icon = Blockbench.getIconNode('inventory_2'); + helper.append(icon); + let span = document.createElement('span'); + span.innerText = collection.name; + helper.append(span); + document.body.append(helper); + Blockbench.addFlag('dragging_collections'); } - helper.style.left = `${e2.clientX}px` - helper.style.top = `${e2.clientY}px` + helper.style.left = `${e2.clientX}px`; + helper.style.top = `${e2.clientY}px`; // drag - $('.drag_hover').removeClass('drag_hover') - $('.collection[order]').attr('order', null) + $('.drag_hover').removeClass('drag_hover'); + $('.collection[order]').attr('order', null); - let target = document.elementFromPoint(e2.clientX, e2.clientY) - ;[drop_target, drop_target_node] = eventTargetToCollection( + let target = document.elementFromPoint(e2.clientX, e2.clientY); + [drop_target, drop_target_node] = eventTargetToCollection( target as HTMLElement - ) + ); if (drop_target) { - var location = e2.clientY - $(drop_target_node).offset().top - order = getOrder(location, drop_target) - drop_target_node.setAttribute('order', order) - drop_target_node.classList.add('drag_hover') + var location = e2.clientY - $(drop_target_node).offset().top; + order = getOrder(location, drop_target); + drop_target_node.setAttribute('order', order); + drop_target_node.classList.add('drag_hover'); } } - last_event = e2 + last_event = e2; } function off(e2) { - if (helper) helper.remove() - removeEventListeners(document, 'mousemove touchmove', move) - removeEventListeners(document, 'mouseup touchend', off) - $('.drag_hover').removeClass('drag_hover') - $('.collection[order]').attr('order', null) - if (Blockbench.isTouch) clearTimeout(timeout) + if (helper) helper.remove(); + removeEventListeners(document, 'mousemove touchmove', move); + removeEventListeners(document, 'mouseup touchend', off); + $('.drag_hover').removeClass('drag_hover'); + $('.collection[order]').attr('order', null); + if (Blockbench.isTouch) clearTimeout(timeout); setTimeout(() => { - Blockbench.removeFlag('dragging_collections') - }, 10) + Blockbench.removeFlag('dragging_collections'); + }, 10); if (active && !Menu.open) { - convertTouchEvent(e2) - let target = document.elementFromPoint(e2.clientX, e2.clientY) - let [target_collection] = eventTargetToCollection(target as HTMLElement) - if (!target_collection || target_collection == collection) return + convertTouchEvent(e2); + let target = document.elementFromPoint(e2.clientX, e2.clientY); + let [target_collection] = eventTargetToCollection( + target as HTMLElement + ); + if (!target_collection || target_collection == collection) return; - let index = Collection.all.indexOf(target_collection) - if (index == -1) return - if (Collection.all.indexOf(collection) < index) index-- - if (order == 1) index++ - if (Collection.all[index] == collection) return + let index = Collection.all.indexOf(target_collection); + if (index == -1) return; + if (Collection.all.indexOf(collection) < index) index--; + if (order == 1) index++; + if (Collection.all[index] == collection) return; - Undo.initEdit({ collections: [collection] }) + Undo.initEdit({ collections: [collection] }); - Collection.all.remove(collection) - Collection.all.splice(index, 0, collection) + Collection.all.remove(collection); + Collection.all.splice(index, 0, collection); - Undo.finishEdit('Reorder collections') + Undo.finishEdit('Reorder collections'); } } if (Blockbench.isTouch) { timeout = setTimeout(() => { - active = true - move(e1) - }, 320) + active = true; + move(e1); + }, 320); } - addEventListeners(document, 'mousemove touchmove', move, { passive: false }) - addEventListeners(document, 'mouseup touchend', off, { passive: false }) + addEventListeners(document, 'mousemove touchmove', move, { passive: false }); + addEventListeners(document, 'mouseup touchend', off, { passive: false }); }, unselect() { - if (Blockbench.hasFlag('dragging_collections')) return + if (Blockbench.hasFlag('dragging_collections')) return; Collection.all.forEach(collection => { - collection.selected = false - }) - updateSelection() + collection.selected = false; + }); + updateSelection(); }, getContentList(collection: Collection) { let types = { group: [], - } + }; for (let child of collection.getChildren()) { // @ts-ignore - let type = child.type - if (!types[type]) types[type] = [] - types[type].push(child) + let type = child.type; + if (!types[type]) types[type] = []; + types[type].push(child); } - let list = [] + let list = []; for (let key in types) { - if (!types[key].length) continue + if (!types[key].length) continue; list.push({ count: types[key].length == 1 ? '' : types[key].length, name: types[key].length == 1 ? types[key][0].name : '', @@ -765,9 +769,9 @@ Interface.definePanels(function () { key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon, - }) + }); } - return list + return list; }, }, template: ` @@ -813,9 +817,9 @@ Interface.definePanels(function () { `, }, menu: new Menu(['create_collection', 'copy']), - }) -}) + }); +}); Object.assign(window, { Collection, -}) +}); diff --git a/js/outliner/element_panel.ts b/js/outliner/element_panel.ts index 9d5a66ce2..17688695e 100644 --- a/js/outliner/element_panel.ts +++ b/js/outliner/element_panel.ts @@ -1,8 +1,8 @@ -import { Blockbench } from '../api' -import { InputForm } from '../interface/form' -import { Interface } from '../interface/interface' -import { Panel } from '../interface/panels' -import { Property } from '../util/property' +import { Blockbench } from '../api'; +import { InputForm } from '../interface/form'; +import { Interface } from '../interface/interface'; +import { Panel } from '../interface/panels'; +import { Property } from '../util/property'; Interface.definePanels(function () { new Panel('transform', { @@ -22,7 +22,7 @@ Interface.definePanels(function () { Toolbars.element_origin, Toolbars.element_rotation, ], - }) + }); let element_properties_panel = new Panel('element', { icon: 'fas.fa-cube', condition: { modes: ['edit'] }, @@ -36,90 +36,90 @@ Interface.definePanels(function () { attached_index: 1, }, form: new InputForm({}), - }) + }); function updateElementForm() { - const { form_config } = element_properties_panel.form + const { form_config } = element_properties_panel.form; for (let key in form_config) { - delete form_config[key] + delete form_config[key]; } - let onchanges = [] + let onchanges = []; let registerInput = (type_id: string, prop_id: string, property: Property) => { - if (!property?.inputs?.element_panel) return - let { input, onChange } = property.inputs.element_panel - let input_id = type_id + '_' + prop_id + if (!property?.inputs?.element_panel) return; + let { input, onChange } = property.inputs.element_panel; + let input_id = type_id + '_' + prop_id; input.condition = { selected: { [type_id]: true }, method: () => Condition(property.condition), - } - if (onChange) onchanges.push(onChange) - form_config[input_id] = input - } + }; + if (onChange) onchanges.push(onChange); + form_config[input_id] = input; + }; for (let type_id in OutlinerElement.types) { - let type = OutlinerElement.types[type_id] + let type = OutlinerElement.types[type_id]; for (let prop_id in type.properties) { - registerInput(type_id, prop_id, type.properties[prop_id]) + registerInput(type_id, prop_id, type.properties[prop_id]); } } for (let prop_id in Group.properties) { - let property = Group.properties[prop_id] + let property = Group.properties[prop_id]; if (property?.inputs?.element_panel) { - registerInput('group', prop_id, Group.properties[prop_id]) + registerInput('group', prop_id, Group.properties[prop_id]); } } element_properties_panel.form.on('input', ({ result, changed_keys }) => { // Only one key should be changed at a time if (changed_keys[0]?.startsWith('group_')) { - let groups = Group.multi_selected - Undo.initEdit({ groups }) + let groups = Group.multi_selected; + Undo.initEdit({ groups }); for (let group of groups) { for (let key in result) { - let property_id = key.replace(group.type + '_', '') + let property_id = key.replace(group.type + '_', ''); // @ts-ignore if (group.constructor.properties[property_id]) { - group[property_id] = result[key] + group[property_id] = result[key]; } } } - Undo.finishEdit('Change group property') - onchanges.forEach(onchange => onchange(result)) + Undo.finishEdit('Change group property'); + onchanges.forEach(onchange => onchange(result)); } else { - let elements = Outliner.selected.slice() - Undo.initEdit({ elements }) + let elements = Outliner.selected.slice(); + Undo.initEdit({ elements }); for (let element of elements) { for (let key in result) { - let property_id = key.replace(element.type + '_', '') + let property_id = key.replace(element.type + '_', ''); // @ts-ignore if (element.constructor.properties[property_id]) { - element[property_id] = result[key] + element[property_id] = result[key]; } } } - Undo.finishEdit('Change element property') - onchanges.forEach(onchange => onchange(result)) + Undo.finishEdit('Change element property'); + onchanges.forEach(onchange => onchange(result)); } - }) - element_properties_panel.form.buildForm() + }); + element_properties_panel.form.buildForm(); } - updateElementForm() + updateElementForm(); Blockbench.on('register_element_type', () => { - updateElementForm() - }) + updateElementForm(); + }); Blockbench.on('update_selection', () => { - let values = {} + let values = {}; for (let type_id in OutlinerElement.types) { - let type = OutlinerElement.types[type_id] - let first_element = type.selected[0] + let type = OutlinerElement.types[type_id]; + let first_element = type.selected[0]; if (first_element) { for (let prop_id in type.properties) { - let property = type.properties[prop_id] + let property = type.properties[prop_id]; if (property?.inputs?.element_panel) { - let input_id = type_id + '_' + prop_id + let input_id = type_id + '_' + prop_id; if (typeof first_element[prop_id] === 'object') { // Prevent object properties from using the same objects across elements. - values[input_id] = { ...first_element[prop_id] } + values[input_id] = { ...first_element[prop_id] }; } else { - values[input_id] = first_element[prop_id] + values[input_id] = first_element[prop_id]; } } } @@ -127,18 +127,18 @@ Interface.definePanels(function () { } if (Group.multi_selected.length) { for (let prop_id in Group.properties) { - let property = Group.properties[prop_id] + let property = Group.properties[prop_id]; if (property?.inputs?.element_panel) { - let input_id = 'group_' + prop_id - values[input_id] = Group.first_selected[prop_id] + let input_id = 'group_' + prop_id; + values[input_id] = Group.first_selected[prop_id]; } } } - element_properties_panel.form.setValues(values) - element_properties_panel.form.update(values) - element_properties_panel.form.updateLabelWidth(true) - }) + element_properties_panel.form.setValues(values); + element_properties_panel.form.update(values); + element_properties_panel.form.updateLabelWidth(true); + }); Toolbars.element_origin.node.after( Interface.createElement('div', { id: 'element_origin_toolbar_anchor' }) - ) -}) + ); +}); diff --git a/js/plugin_loader.ts b/js/plugin_loader.ts index 5cbc838b7..0b57311aa 100644 --- a/js/plugin_loader.ts +++ b/js/plugin_loader.ts @@ -1,13 +1,13 @@ -import { Blockbench } from './api' -import StateMemory from './util/state_memory' -import { Dialog } from './interface/dialog' -import { settings, Settings, SettingsProfile } from './interface/settings' -import { ModelLoader, StartScreen } from './interface/start_screen' -import { sort_collator } from './misc' -import { separateThousands } from './util/math_util' -import { getDateDisplay } from './util/util' -import { Filesystem } from './file_system' -import { Panels } from './interface/interface' +import { Blockbench } from './api'; +import StateMemory from './util/state_memory'; +import { Dialog } from './interface/dialog'; +import { settings, Settings, SettingsProfile } from './interface/settings'; +import { ModelLoader, StartScreen } from './interface/start_screen'; +import { sort_collator } from './misc'; +import { separateThousands } from './util/math_util'; +import { getDateDisplay } from './util/util'; +import { Filesystem } from './file_system'; +import { Panels } from './interface/interface'; import { app, fs, @@ -15,12 +15,12 @@ import { getPluginScopedRequire, https, revokePluginPermissions, -} from './native_apis' +} from './native_apis'; interface FileResult { - name: string - path: string - content: string | ArrayBuffer + name: string; + path: string; + content: string | ArrayBuffer; } export const Plugins = { @@ -53,288 +53,288 @@ export const Plugins = { * Dev reload all side-loaded plugins */ devReload() { - let reloads = 0 + let reloads = 0; for (let i = Plugins.all.length - 1; i >= 0; i--) { - let plugin = Plugins.all[i] + let plugin = Plugins.all[i]; if (plugin.source == 'file' && plugin.isReloadable()) { - Plugins.all[i].reload() - reloads++ + Plugins.all[i].reload(); + reloads++; } } - Blockbench.showQuickMessage(tl('message.plugin_reload', [reloads])) - console.log('Reloaded ' + reloads + ' plugin' + pluralS(reloads)) + Blockbench.showQuickMessage(tl('message.plugin_reload', [reloads])); + console.log('Reloaded ' + reloads + ' plugin' + pluralS(reloads)); }, /** * Update sort order of existing plugins */ sort() { Plugins.all.sort((a, b) => { - if (a.tags.find(tag => tag.match(/deprecated/i))) return 1 - if (b.tags.find(tag => tag.match(/deprecated/i))) return -1 + if (a.tags.find(tag => tag.match(/deprecated/i))) return 1; + if (b.tags.find(tag => tag.match(/deprecated/i))) return -1; let download_difference = - (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0) + (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0); if (download_difference) { - return download_difference + return download_difference; } else { - return sort_collator.compare(a.title, b.title) + return sort_collator.compare(a.title, b.title); } - }) + }); }, -} -StateMemory.init('installed_plugins', 'array') +}; +StateMemory.init('installed_plugins', 'array'); // @ts-ignore Plugins.installed = StateMemory.installed_plugins = StateMemory.installed_plugins.filter( p => p && typeof p == 'object' -) +); -type PluginVariant = 'desktop' | 'web' | 'both' -type PluginSource = 'store' | 'file' | 'url' +type PluginVariant = 'desktop' | 'web' | 'both'; +type PluginSource = 'store' | 'file' | 'url'; type PluginDetails = { - version: string - last_modified: string - creation_date: string - last_modified_full: string - creation_date_full: string - min_version: string - max_version: string - website: string - repository: string - bug_tracker: string - contributors: string - author: string - variant: PluginVariant | string - permissions: string - weekly_installations: string -} + version: string; + last_modified: string; + creation_date: string; + last_modified_full: string; + creation_date_full: string; + min_version: string; + max_version: string; + website: string; + repository: string; + bug_tracker: string; + contributors: string; + author: string; + variant: PluginVariant | string; + permissions: string; + weekly_installations: string; +}; type PluginInstallation = { - id: string - version: string - path: string - source: PluginSource - dependencies?: string[] - disabled?: boolean -} + id: string; + version: string; + path: string; + source: PluginSource; + dependencies?: string[]; + disabled?: boolean; +}; type PluginChangelog = Record< string, { - title: string - author?: string - date?: string + title: string; + author?: string; + date?: string; categories: { - title: string - list: string[] - }[] + title: string; + list: string[]; + }[]; } -> +>; interface PluginOptions { - title: string - author: string + title: string; + author: string; /** * Description text in the plugin browser */ - description: string + description: string; /** * The about text appears when the user unfolds the plugin in the plugin browser. It can contain additional information and usage instructions */ - about?: string + about?: string; /** * The version of the plugin. */ - version?: string - icon: string + version?: string; + icon: string; /** * Plugin tags that will show up in the plugin store. You can provide up to 3 tags. */ - tags?: [string, string?, string?] + tags?: [string, string?, string?]; /** * Where the plugin can be installed. Desktop refers to the electron app, web refers to the web app and PWA */ - variant: 'both' | 'desktop' | 'web' + variant: 'both' | 'desktop' | 'web'; /** * Minimum Blockbench version in which the plugin can be installed */ - min_version?: string + min_version?: string; /** * Maximum Blockbench version in which the plugin can be installed */ - max_version?: string + max_version?: string; /** * Set to true if the plugin must finish loading before a project is opened, i. e. because it adds a format */ - await_loading?: boolean + await_loading?: boolean; /** * Use the new repository format where plugin, iron, and about are stored in a separate folder */ - new_repository_format?: boolean + new_repository_format?: boolean; /** * Can be used to specify which features a plugin adds. This allows Blockbench to be aware of and suggest even plugins that are not installed. */ contributes?: { - formats: string[] - } - has_changelog?: boolean + formats: string[]; + }; + has_changelog?: boolean; /** * In combination with a "Deprecated" tag, this can be used to provide context on why a plugin is deprecated */ - deprecation_note?: string + deprecation_note?: string; /* * Link to the plugin's website */ - website?: string + website?: string; /* * Link to the repository that contains the source for the plugin */ - repository?: string + repository?: string; /* * Link to where users can report issues with the plugin */ - bug_tracker?: string + bug_tracker?: string; /* * List of secondary contributors to the plugin, excluding the main author(s) */ - contributors?: string[] - disabled?: boolean + contributors?: string[]; + disabled?: boolean; /** * Runs when the plugin loads */ - onload?(): void + onload?(): void; /** * Runs when the plugin unloads */ - onunload?(): void + onunload?(): void; /** * Runs when the user manually installs the plugin */ - oninstall?(): void + oninstall?(): void; /** * Runs when the user manually uninstalls the plugin */ - onuninstall?(): void + onuninstall?(): void; } interface PluginSetupOptions { - disabled?: boolean + disabled?: boolean; } export class Plugin { - id: string - installed: boolean - title: string - author: string - description: string - about: string - icon: string - tags: string[] - dependencies: string[] - contributors: string[] - version: string - variant: PluginVariant - min_version: string - max_version: string - deprecation_note: string - path: string - website: string - repository: string - bug_tracker: string - source: PluginSource - creation_date: string | number - contributes: {} - await_loading: boolean - has_changelog: boolean - changelog: null | PluginChangelog - about_fetched: boolean - changelog_fetched: boolean - disabled: boolean - new_repository_format: boolean - cache_version: number - menu: Menu - details: null | PluginDetails - - onload?: () => void - onunload?: () => void - oninstall?: () => void - onuninstall?: () => void + id: string; + installed: boolean; + title: string; + author: string; + description: string; + about: string; + icon: string; + tags: string[]; + dependencies: string[]; + contributors: string[]; + version: string; + variant: PluginVariant; + min_version: string; + max_version: string; + deprecation_note: string; + path: string; + website: string; + repository: string; + bug_tracker: string; + source: PluginSource; + creation_date: string | number; + contributes: {}; + await_loading: boolean; + has_changelog: boolean; + changelog: null | PluginChangelog; + about_fetched: boolean; + changelog_fetched: boolean; + disabled: boolean; + new_repository_format: boolean; + cache_version: number; + menu: Menu; + details: null | PluginDetails; + + onload?: () => void; + onunload?: () => void; + oninstall?: () => void; + onuninstall?: () => void; constructor(id: string = 'unknown', data?: PluginOptions | PluginSetupOptions) { - this.id = id - this.installed = false - this.title = '' - this.author = '' - this.description = '' - this.about = '' - this.icon = '' - this.tags = [] - this.dependencies = [] - this.contributors = [] - this.version = '0.0.1' - this.variant = 'both' - this.min_version = '' - this.max_version = '' - this.deprecation_note = '' - this.website = '' - this.source = 'store' - this.creation_date = 0 - this.contributes = {} - this.await_loading = false - this.has_changelog = false - this.changelog = null - this.details = null - this.about_fetched = false - this.changelog_fetched = false - this.disabled = false - this.new_repository_format = false - this.cache_version = 0 - - this.extend(data) - - Plugins.all.safePush(this) + this.id = id; + this.installed = false; + this.title = ''; + this.author = ''; + this.description = ''; + this.about = ''; + this.icon = ''; + this.tags = []; + this.dependencies = []; + this.contributors = []; + this.version = '0.0.1'; + this.variant = 'both'; + this.min_version = ''; + this.max_version = ''; + this.deprecation_note = ''; + this.website = ''; + this.source = 'store'; + this.creation_date = 0; + this.contributes = {}; + this.await_loading = false; + this.has_changelog = false; + this.changelog = null; + this.details = null; + this.about_fetched = false; + this.changelog_fetched = false; + this.disabled = false; + this.new_repository_format = false; + this.cache_version = 0; + + this.extend(data); + + Plugins.all.safePush(this); } extend(data) { - if (!(data instanceof Object)) return this - Merge.boolean(this, data, 'installed') - Merge.string(this, data, 'title') - Merge.string(this, data, 'author') - Merge.string(this, data, 'description') - Merge.string(this, data, 'about') - Merge.string(this, data, 'icon') - Merge.string(this, data, 'version') - Merge.string(this, data, 'variant') - Merge.string(this, data, 'min_version') - Merge.string(this, data, 'max_version') - Merge.string(this, data, 'deprecation_note') - Merge.string(this, data, 'website') - Merge.string(this, data, 'repository') - Merge.string(this, data, 'bug_tracker') - Merge.boolean(this, data, 'await_loading') - Merge.boolean(this, data, 'has_changelog') - Merge.boolean(this, data, 'disabled') - if (data.creation_date) this.creation_date = Date.parse(data.creation_date) - if (data.tags instanceof Array) this.tags.safePush(...data.tags.slice(0, 3)) - if (data.contributors instanceof Array) this.contributors.safePush(...data.contributors) - if (data.dependencies instanceof Array) this.dependencies.safePush(...data.dependencies) - - if (data.new_repository_format) this.new_repository_format = true + if (!(data instanceof Object)) return this; + Merge.boolean(this, data, 'installed'); + Merge.string(this, data, 'title'); + Merge.string(this, data, 'author'); + Merge.string(this, data, 'description'); + Merge.string(this, data, 'about'); + Merge.string(this, data, 'icon'); + Merge.string(this, data, 'version'); + Merge.string(this, data, 'variant'); + Merge.string(this, data, 'min_version'); + Merge.string(this, data, 'max_version'); + Merge.string(this, data, 'deprecation_note'); + Merge.string(this, data, 'website'); + Merge.string(this, data, 'repository'); + Merge.string(this, data, 'bug_tracker'); + Merge.boolean(this, data, 'await_loading'); + Merge.boolean(this, data, 'has_changelog'); + Merge.boolean(this, data, 'disabled'); + if (data.creation_date) this.creation_date = Date.parse(data.creation_date); + if (data.tags instanceof Array) this.tags.safePush(...data.tags.slice(0, 3)); + if (data.contributors instanceof Array) this.contributors.safePush(...data.contributors); + if (data.dependencies instanceof Array) this.dependencies.safePush(...data.dependencies); + + if (data.new_repository_format) this.new_repository_format = true; if (this.min_version != '' && !compareVersions('4.8.0', this.min_version)) { - this.new_repository_format = true + this.new_repository_format = true; } if (typeof data.contributes == 'object') { - this.contributes = data.contributes + this.contributes = data.contributes; } - Merge.function(this, data, 'onload') - Merge.function(this, data, 'onunload') - Merge.function(this, data, 'oninstall') - Merge.function(this, data, 'onuninstall') - return this + Merge.function(this, data, 'onload'); + Merge.function(this, data, 'onunload'); + Merge.function(this, data, 'oninstall'); + Merge.function(this, data, 'onuninstall'); + return this; } get name() { - return this.title + return this.title; } async install() { if (this.tags.includes('Deprecated') || this.deprecation_note) { - let message = tl('message.plugin_deprecated.message') + let message = tl('message.plugin_deprecated.message'); if (this.deprecation_note) { - message += '\n\n*' + this.deprecation_note + '*' + message += '\n\n*' + this.deprecation_note + '*'; } let answer = await new Promise(resolve => { Blockbench.showMessageBox( @@ -346,29 +346,29 @@ export class Plugin { buttons: ['dialog.cancel', 'message.plugin_deprecated.install_anyway'], }, resolve - ) - }) - if (answer == 0) return + ); + }); + if (answer == 0) return; } - return await this.download(true) + return await this.download(true); } async load(first: boolean = false, cb?: (this: Plugin) => void) { - var scope = this - Plugins.registered[this.id] = this + var scope = this; + Plugins.registered[this.id] = this; return await new Promise((resolve, reject) => { - let path = Plugins.path + scope.id + '.js' + let path = Plugins.path + scope.id + '.js'; if (!isApp && this.new_repository_format) { - path = `${Plugins.path}${scope.id}/${scope.id}.js` + path = `${Plugins.path}${scope.id}/${scope.id}.js`; } this.#runPluginFile(path) .then(content => { - if (cb) cb.bind(scope)() + if (cb) cb.bind(scope)(); if (first && scope.oninstall) { - scope.oninstall() + scope.oninstall(); } if (first) - Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])) - resolve() + Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])); + resolve(); }) .catch(error => { if (isApp) { @@ -376,53 +376,53 @@ export class Plugin { 'Could not find file of plugin "' + scope.id + '". Uninstalling it instead.' - ) - scope.uninstall() + ); + scope.uninstall(); } if (first) Blockbench.showQuickMessage( tl('message.installed_plugin_fail', [this.title]) - ) - reject() - console.error(error) - }) - this.#remember() - scope.installed = true - }) + ); + reject(); + console.error(error); + }); + this.#remember(); + scope.installed = true; + }); } async installDependencies(first) { - let required_dependencies = [] + let required_dependencies = []; for (let id of this.dependencies) { - let saved_install = !first && Plugins.installed.find(p => p.id == id) + let saved_install = !first && Plugins.installed.find(p => p.id == id); if (saved_install) { - continue + continue; } - let plugin = Plugins.all.find(p => p.id == id) + let plugin = Plugins.all.find(p => p.id == id); if (plugin) { - if (plugin.installed == false) required_dependencies.push(plugin) - continue + if (plugin.installed == false) required_dependencies.push(plugin); + continue; } - required_dependencies.push(id) + required_dependencies.push(id); } if (required_dependencies.length) { let failed_dependency = required_dependencies.find(p => { - return !p.isInstallable || p.isInstallable() != true - }) + return !p.isInstallable || p.isInstallable() != true; + }); if (failed_dependency) { - let error_message = failed_dependency + let error_message = failed_dependency; if (failed_dependency instanceof Plugin) { - error_message = `**${failed_dependency.title}**: ${failed_dependency.isInstallable()}` + error_message = `**${failed_dependency.title}**: ${failed_dependency.isInstallable()}`; } Blockbench.showMessageBox({ title: 'message.plugin_dependencies.title', message: `Updating **${this.title || this.id}**:\n\n${tl('message.plugin_dependencies.invalid')}\n\n${error_message}`, - }) - return false + }); + return false; } let list = required_dependencies.map( p => `**${p.title}** ${tl('dialog.plugins.author', [p.author])}` - ) + ); let response = await new Promise(resolve => { Blockbench.showMessageBox( { @@ -435,39 +435,39 @@ export class Plugin { width: 512, }, button => { - resolve(button == 0) + resolve(button == 0); } - ) - }) + ); + }); if (!response) { - if (this.installed) this.uninstall() - return false + if (this.installed) this.uninstall(); + return false; } for (let dependency of required_dependencies) { - await dependency.install() + await dependency.install(); } } - return true + return true; } async download(first = false) { - let response = await this.installDependencies(first) - if (response == false) return + let response = await this.installDependencies(first); + if (response == false) return; - var scope = this + var scope = this; function register() { - if (!Plugins.json[scope.id]) return + if (!Plugins.json[scope.id]) return; jQuery.ajax({ url: 'https://blckbn.ch/api/event/install_plugin', type: 'POST', data: { plugin: scope.id, }, - }) + }); } if (!isApp) { - if (first) register() - return await scope.load(first) + if (first) register(); + return await scope.load(first); } // Download files @@ -476,371 +476,371 @@ export class Plugin { target_filename?: string, callback?: () => void ) { - var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename)) + var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename)); // @ts-ignore https.get(Plugins.api_path + '/' + origin_filename, function (response) { - response.pipe(file) - if (callback) response.on('end', callback) - }) + response.pipe(file); + if (callback) response.on('end', callback); + }); } return await new Promise(async (resolve, reject) => { // New system if (this.new_repository_format) { copyFileToDrive(`${this.id}/${this.id}.js`, `${this.id}.js`, () => { - if (first) register() + if (first) register(); setTimeout(async function () { - await scope.load(first) - resolve() - }, 20) - }) + await scope.load(first); + resolve(); + }, 20); + }); if (this.hasImageIcon()) { - copyFileToDrive(`${this.id}/${this.icon}`, this.id + '.' + this.icon) + copyFileToDrive(`${this.id}/${this.icon}`, this.id + '.' + this.icon); } - await this.fetchAbout() + await this.fetchAbout(); if (this.about) { fs.writeFileSync( PathModule.join(Plugins.path, this.id + '.about.md'), this.about, 'utf-8' - ) + ); } } else { // Legacy system copyFileToDrive(`${this.id}.js`, `${this.id}.js`, () => { - if (first) register() + if (first) register(); setTimeout(async function () { - await scope.load(first) - resolve() - }, 20) - }) + await scope.load(first); + resolve(); + }, 20); + }); } - }) + }); } async loadFromFile(file: Filesystem.FileResult, first = false) { - var scope = this - if (!isApp && !first) return this + var scope = this; + if (!isApp && !first) return this; if (first) { if (isApp) { - if (!confirm(tl('message.load_plugin_app'))) return + if (!confirm(tl('message.load_plugin_app'))) return; } else { - if (!confirm(tl('message.load_plugin_web'))) return + if (!confirm(tl('message.load_plugin_web'))) return; } } - this.id = pathToName(file.path) - Plugins.registered[this.id] = this - Plugins.all.safePush(this) - this.source = 'file' - this.tags.safePush('Local') + this.id = pathToName(file.path); + Plugins.registered[this.id] = this; + Plugins.all.safePush(this); + this.source = 'file'; + this.tags.safePush('Local'); if (isApp) { let content = await this.#runPluginFile(file.path).catch(error => { - console.error(error) - }) + console.error(error); + }); if (content) { if (first && scope.oninstall) { - scope.oninstall() + scope.oninstall(); } - scope.path = file.path + scope.path = file.path; } } else { - this.#runCode(file.content as string) + this.#runCode(file.content as string); if (first && scope.oninstall) { - scope.oninstall() + scope.oninstall(); } } - this.installed = true - this.#remember() - Plugins.sort() + this.installed = true; + this.#remember(); + Plugins.sort(); } async loadFromURL(url: string, first: boolean = false) { if (first) { if (isApp) { - if (!confirm(tl('message.load_plugin_app'))) return + if (!confirm(tl('message.load_plugin_app'))) return; } else { - if (!confirm(tl('message.load_plugin_web'))) return + if (!confirm(tl('message.load_plugin_web'))) return; } } - this.id = pathToName(url) - Plugins.registered[this.id] = this - Plugins.all.safePush(this) - this.tags.safePush('Remote') + this.id = pathToName(url); + Plugins.registered[this.id] = this; + Plugins.all.safePush(this); + this.tags.safePush('Remote'); - this.source = 'url' + this.source = 'url'; let content = await this.#runPluginFile(url).catch(async error => { if (isApp) { - await this.load() + await this.load(); } - console.error(error) - }) + console.error(error); + }); if (content) { if (first && this.oninstall) { - this.oninstall() + this.oninstall(); } - this.installed = true - this.path = url - this.#remember() - Plugins.sort() + this.installed = true; + this.path = url; + this.#remember(); + Plugins.sort(); // Save if (isApp) { await new Promise((resolve, reject) => { - let file = fs.createWriteStream(Plugins.path + this.id + '.js') + let file = fs.createWriteStream(Plugins.path + this.id + '.js'); // @ts-ignore https .get(url, response => { - response.pipe(file) - response.on('end', resolve) + response.pipe(file); + response.on('end', resolve); }) - .on('error', reject) - }) + .on('error', reject); + }); } } - return this + return this; } #remember(id = this.id, path = this.path) { - let entry = Plugins.installed.find(plugin => plugin.id == this.id) - let already_exists = !!entry - if (!entry) entry = {} as PluginInstallation + let entry = Plugins.installed.find(plugin => plugin.id == this.id); + let already_exists = !!entry; + if (!entry) entry = {} as PluginInstallation; - entry.id = id - entry.version = this.version - entry.path = path - entry.source = this.source - entry.disabled = this.disabled ? true : undefined - entry.dependencies = this.dependencies?.length ? this.dependencies.slice() : undefined + entry.id = id; + entry.version = this.version; + entry.path = path; + entry.source = this.source; + entry.disabled = this.disabled ? true : undefined; + entry.dependencies = this.dependencies?.length ? this.dependencies.slice() : undefined; - if (!already_exists) Plugins.installed.push(entry) + if (!already_exists) Plugins.installed.push(entry); - StateMemory.save('installed_plugins') - return this + StateMemory.save('installed_plugins'); + return this; } uninstall() { try { - this.unload() + this.unload(); if (this.onuninstall) { - this.onuninstall() + this.onuninstall(); } } catch (err) { - console.error(`Error in unload or uninstall method of "${this.id}": `, err) + console.error(`Error in unload or uninstall method of "${this.id}": `, err); } - delete Plugins.registered[this.id] - let in_installed = Plugins.installed.find(plugin => plugin.id == this.id) - Plugins.installed.remove(in_installed) - StateMemory.save('installed_plugins') - this.installed = false - this.disabled = false + delete Plugins.registered[this.id]; + let in_installed = Plugins.installed.find(plugin => plugin.id == this.id); + Plugins.installed.remove(in_installed); + StateMemory.save('installed_plugins'); + this.installed = false; + this.disabled = false; if (isApp && this.source !== 'store') { - Plugins.all.remove(this) + Plugins.all.remove(this); } if (isApp && this.source != 'file') { function removeCachedFile(filepath) { if (fs.existsSync(filepath)) { fs.unlink(filepath, err => { - if (err) console.log(err) - }) + if (err) console.log(err); + }); } } - removeCachedFile(Plugins.path + this.id + '.js') - removeCachedFile(Plugins.path + this.id + '.' + this.icon) - removeCachedFile(Plugins.path + this.id + '.about.md') + removeCachedFile(Plugins.path + this.id + '.js'); + removeCachedFile(Plugins.path + this.id + '.' + this.icon); + removeCachedFile(Plugins.path + this.id + '.about.md'); } - StateMemory.save('installed_plugins') - return this + StateMemory.save('installed_plugins'); + return this; } unload() { if (this.onunload) { - this.onunload() + this.onunload(); } - return this + return this; } reload() { - if (!isApp && this.source == 'file') return this - - this.cache_version++ - this.unload() - this.tags.empty() - this.contributors.empty() - this.dependencies.empty() - Plugins.all.remove(this) - this.details = null - let had_changelog = this.changelog_fetched - this.changelog_fetched = false + if (!isApp && this.source == 'file') return this; + + this.cache_version++; + this.unload(); + this.tags.empty(); + this.contributors.empty(); + this.dependencies.empty(); + Plugins.all.remove(this); + this.details = null; + let had_changelog = this.changelog_fetched; + this.changelog_fetched = false; if (this.source == 'file') { - this.loadFromFile({ path: this.path, name: this.path, content: '' }, false) + this.loadFromFile({ path: this.path, name: this.path, content: '' }, false); } else if (this.source == 'url') { - this.loadFromURL(this.path, false) + this.loadFromURL(this.path, false); } - this.fetchAbout(true) + this.fetchAbout(true); if (had_changelog && this.has_changelog) { - this.fetchChangelog(true) + this.fetchChangelog(true); } - return this + return this; } async #runPluginFile(path: string) { - let file_content: any + let file_content: any; if (path.startsWith('http')) { if (!path.startsWith('https')) { - throw 'Cannot load plugins over http: ' + path + throw 'Cannot load plugins over http: ' + path; } await new Promise((resolve, reject) => { $.ajax({ cache: false, url: path, success(data) { - file_content = data - resolve() + file_content = data; + resolve(); }, error() { - reject('Failed to load plugin ' + this.id) + reject('Failed to load plugin ' + this.id); }, - }) - }) + }); + }); } else if (isApp) { - file_content = fs.readFileSync(path, { encoding: 'utf-8' }) + file_content = fs.readFileSync(path, { encoding: 'utf-8' }); } else { - throw 'Failed to load plugin: Unknown URL format' + throw 'Failed to load plugin: Unknown URL format'; } - this.#runCode(file_content) - return file_content + this.#runCode(file_content); + return file_content; } #runCode(code: string) { if (typeof code != 'string' || code.length < 20) { - throw `Issue loading plugin "${this.id}": Plugin file empty` + throw `Issue loading plugin "${this.id}": Plugin file empty`; } try { const func = new Function( 'requireNativeModule', 'require', code + `\n//# sourceURL=PLUGINS/(Plugin):${this.id}.js` - ) - const scoped_require = isApp ? getPluginScopedRequire(this) : undefined - func(scoped_require, scoped_require) + ); + const scoped_require = isApp ? getPluginScopedRequire(this) : undefined; + func(scoped_require, scoped_require); } catch (err) { - console.error(err) + console.error(err); } } toggleDisabled() { if (!this.disabled) { - this.disabled = true - this.unload() + this.disabled = true; + this.unload(); } else { if (this.onload) { - this.onload() + this.onload(); } - this.disabled = false + this.disabled = false; } - this.#remember() + this.#remember(); } showContextMenu(event: MouseEvent) { - Plugin.menu.open(event, this) + Plugin.menu.open(event, this); } isReloadable() { return ( this.installed && !this.disabled && ((this.source == 'file' && isApp) || this.source == 'url') - ) + ); } isInstallable() { - var scope = this + var scope = this; var result: string | boolean = scope.variant === 'both' || - (isApp === (scope.variant === 'desktop') && isApp !== (scope.variant === 'web')) + (isApp === (scope.variant === 'desktop') && isApp !== (scope.variant === 'web')); if (result && scope.min_version) { - result = Blockbench.isOlderThan(scope.min_version) ? 'outdated_client' : true + result = Blockbench.isOlderThan(scope.min_version) ? 'outdated_client' : true; } if (result && scope.max_version) { - result = Blockbench.isNewerThan(scope.max_version) ? 'outdated_plugin' : true + result = Blockbench.isNewerThan(scope.max_version) ? 'outdated_plugin' : true; } if (result === false) { - result = scope.variant === 'web' ? 'web_only' : 'app_only' + result = scope.variant === 'web' ? 'web_only' : 'app_only'; } - return result === true ? true : tl('dialog.plugins.' + result) + return result === true ? true : tl('dialog.plugins.' + result); } hasImageIcon() { - return this.icon.endsWith('.png') || this.icon.endsWith('.svg') + return this.icon.endsWith('.png') || this.icon.endsWith('.svg'); } getIcon() { if (this.hasImageIcon()) { if (isApp) { if (this.installed && this.source == 'store') { - return Plugins.path + this.id + '.' + this.icon + return Plugins.path + this.id + '.' + this.icon; } if (this.source != 'store') return this.path.replace( /\w+\.js$/, this.icon + (this.cache_version ? '?' + this.cache_version : '') - ) + ); } - return `${Plugins.api_path}/${this.id}/${this.icon}` + return `${Plugins.api_path}/${this.id}/${this.icon}`; } - return this.icon + return this.icon; } async fetchAbout(force: boolean = false) { if (((!this.about_fetched && !this.about) || force) && this.new_repository_format) { if (isApp && this.installed) { try { - let about_path + let about_path; if (this.source == 'store') { - about_path = PathModule.join(Plugins.path, this.id + '.about.md') + about_path = PathModule.join(Plugins.path, this.id + '.about.md'); } else { - about_path = this.path.replace(/\w+\.js$/, 'about.md') + about_path = this.path.replace(/\w+\.js$/, 'about.md'); } - let content = fs.readFileSync(about_path, { encoding: 'utf-8' }) - this.about = content - this.about_fetched = true - return + let content = fs.readFileSync(about_path, { encoding: 'utf-8' }); + this.about = content; + this.about_fetched = true; + return; } catch (err) { - console.error('failed to get about for plugin ' + this.id) + console.error('failed to get about for plugin ' + this.id); } } - let url = `${Plugins.api_path}/${this.id}/about.md` + let url = `${Plugins.api_path}/${this.id}/about.md`; let result = await fetch(url).catch(() => { - console.error('about.md missing for plugin ' + this.id) - }) + console.error('about.md missing for plugin ' + this.id); + }); if (result && result.ok) { - this.about = await result.text() + this.about = await result.text(); } - this.about_fetched = true + this.about_fetched = true; } } async fetchChangelog(force = false) { if ((!this.changelog_fetched && !this.changelog) || force) { function reverseOrder(input) { - let output = {} + let output = {}; Object.keys(input).forEachReverse(key => { - output[key] = input[key] - }) - return output + output[key] = input[key]; + }); + return output; } if (isApp && this.installed && this.source != 'store') { try { - let changelog_path = this.path.replace(/\w+\.js$/, 'changelog.json') - let content = fs.readFileSync(changelog_path, { encoding: 'utf-8' }) - this.changelog = reverseOrder(JSON.parse(content)) - this.changelog_fetched = true - return + let changelog_path = this.path.replace(/\w+\.js$/, 'changelog.json'); + let content = fs.readFileSync(changelog_path, { encoding: 'utf-8' }); + this.changelog = reverseOrder(JSON.parse(content)); + this.changelog_fetched = true; + return; } catch (err) { - console.error('failed to get changelog for plugin ' + this.id, err) + console.error('failed to get changelog for plugin ' + this.id, err); } } - let url = `${Plugins.api_path}/${this.id}/changelog.json` + let url = `${Plugins.api_path}/${this.id}/changelog.json`; let result = await fetch(url).catch(() => { - console.error('changelog.json missing for plugin ' + this.id) - }) + console.error('changelog.json missing for plugin ' + this.id); + }); if (result && result.ok) { - this.changelog = reverseOrder(await result.json()) + this.changelog = reverseOrder(await result.json()); } - this.changelog_fetched = true + this.changelog_fetched = true; } } getPluginDetails(force_refresh: boolean = false) { - if (this.details && !force_refresh) return this.details + if (this.details && !force_refresh) return this.details; this.details = { version: this.version, last_modified: 'N/A', @@ -857,60 +857,63 @@ export class Plugin { variant: this.variant == 'both' ? 'All' : this.variant, permissions: '', weekly_installations: separateThousands(Plugins.download_stats[this.id] || 0), - } + }; if (isApp) { - let perms = getPluginPermissions(this) + let perms = getPluginPermissions(this); if (perms) { - let perms_list = [] + let perms_list = []; for (let key in perms) { if (key == 'fs' && perms[key].directories) { - perms_list.push(`Scoped FS (${perms[key].directories.join(', ')})`) + perms_list.push(`Scoped FS (${perms[key].directories.join(', ')})`); } else { - perms_list.push(key) + perms_list.push(key); } } - this.details.permissions = perms_list.join(', ') + this.details.permissions = perms_list.join(', '); } } let trackDate = (input_date, key) => { - let date = getDateDisplay(input_date) - this.details[key] = date.short - this.details[key + '_full'] = date.full - } + let date = getDateDisplay(input_date); + this.details[key] = date.short; + this.details[key + '_full'] = date.full; + }; if (this.source == 'store') { if (!this.details.bug_tracker) { - this.details.bug_tracker = `https://github.com/JannisX11/blockbench-plugins/issues/new?title=[${this.title.replace(/[+&]/g, 'and')}]` + this.details.bug_tracker = `https://github.com/JannisX11/blockbench-plugins/issues/new?title=[${this.title.replace(/[+&]/g, 'and')}]`; } if (!this.details.repository) { - this.details.repository = `https://github.com/JannisX11/blockbench-plugins/tree/master/plugins/${this.id + (this.new_repository_format ? '' : '.js')}` + this.details.repository = `https://github.com/JannisX11/blockbench-plugins/tree/master/plugins/${this.id + (this.new_repository_format ? '' : '.js')}`; } let github_path = - (this.new_repository_format ? this.id + '/' + this.id : this.id) + '.js' - let commit_url = `https://api.github.com/repos/JannisX11/blockbench-plugins/commits?path=plugins/${github_path}` + (this.new_repository_format ? this.id + '/' + this.id : this.id) + '.js'; + let commit_url = `https://api.github.com/repos/JannisX11/blockbench-plugins/commits?path=plugins/${github_path}`; fetch(commit_url) .catch(err => { - console.error('Cannot access commit info for ' + this.id, err) + console.error('Cannot access commit info for ' + this.id, err); }) .then(async response => { - if (!response) return - let commits = await response.json().catch(err => console.error(err)) - if (!commits || !commits.length) return - trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified') + if (!response) return; + let commits = await response.json().catch(err => console.error(err)); + if (!commits || !commits.length) return; + trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified'); if (!this.creation_date) { - trackDate(Date.parse(commits.last().commit.committer.date), 'creation_date') + trackDate( + Date.parse(commits.last().commit.committer.date), + 'creation_date' + ); } - }) + }); } if (this.creation_date) { - trackDate(this.creation_date, 'creation_date') + trackDate(this.creation_date, 'creation_date'); } - return this.details + return this.details; } - static selected: Plugin | null = null + static selected: Plugin | null = null; static menu = new Menu([ new MenuSeparator('general'), @@ -919,14 +922,14 @@ export class Plugin { icon: 'share', condition: plugin => Plugins.json[plugin.id], click(plugin) { - let url = `https://www.blockbench.net/plugins/${plugin.id}` + let url = `https://www.blockbench.net/plugins/${plugin.id}`; new Dialog('share_plugin', { title: tl('generic.share') + ': ' + plugin.title, icon: 'extension', form: { link: { type: 'text', value: url, readonly: true, share_text: true }, }, - }).show() + }).show(); }, }, new MenuSeparator('installation'), @@ -935,7 +938,7 @@ export class Plugin { icon: 'add', condition: plugin => !plugin.installed && plugin.isInstallable() == true, click(plugin) { - plugin.install() + plugin.install(); }, }, { @@ -943,7 +946,7 @@ export class Plugin { icon: 'delete', condition: plugin => plugin.installed, click(plugin) { - plugin.uninstall() + plugin.uninstall(); }, }, { @@ -951,7 +954,7 @@ export class Plugin { icon: 'bedtime', condition: plugin => plugin.installed && !plugin.disabled, click(plugin) { - plugin.toggleDisabled() + plugin.toggleDisabled(); }, }, { @@ -959,7 +962,7 @@ export class Plugin { icon: 'bedtime', condition: plugin => plugin.installed && plugin.disabled, click(plugin) { - plugin.toggleDisabled() + plugin.toggleDisabled(); }, }, { @@ -967,12 +970,12 @@ export class Plugin { icon: 'key_off', condition: isApp && ((plugin: Plugin) => getPluginPermissions(plugin)), click(plugin: Plugin) { - let revoked = revokePluginPermissions(plugin) + let revoked = revokePluginPermissions(plugin); Blockbench.showQuickMessage( `Revoked ${revoked.length} permissions. Restart to apply`, 2000 - ) - plugin.getPluginDetails(true) + ); + plugin.getPluginDetails(true); }, }, new MenuSeparator('developer'), @@ -981,7 +984,7 @@ export class Plugin { icon: 'refresh', condition: plugin => plugin.installed && plugin.isReloadable(), click(plugin) { - plugin.reload() + plugin.reload(); }, }, { @@ -989,56 +992,56 @@ export class Plugin { icon: 'folder', condition: plugin => isApp && plugin.source == 'file', click(plugin) { - Filesystem.showFileInFolder(plugin.path) + Filesystem.showFileInFolder(plugin.path); }, }, - ]) + ]); static register(id: string, data: PluginOptions) { if (typeof id !== 'string' || typeof data !== 'object') { - console.warn('Plugin.register: not enough arguments, string and object required.') - return + console.warn('Plugin.register: not enough arguments, string and object required.'); + return; } - var plugin = Plugins.registered[id] + var plugin = Plugins.registered[id]; if (!plugin) { - plugin = Plugins.registered.unknown + plugin = Plugins.registered.unknown; if (plugin) { - delete Plugins.registered.unknown - plugin.id = id - Plugins.registered[id] = plugin + delete Plugins.registered.unknown; + plugin.id = id; + Plugins.registered[id] = plugin; } } if (!plugin) { Blockbench.showMessageBox({ translateKey: 'load_plugin_failed', message: tl('message.load_plugin_failed.message', [id]), - }) - return + }); + return; } - plugin.extend(data) + plugin.extend(data); if (plugin.isInstallable() == true && plugin.disabled == false) { if (plugin.onload instanceof Function) { - Plugins.currently_loading = id - plugin.onload() - Plugins.currently_loading = '' + Plugins.currently_loading = id; + plugin.onload(); + Plugins.currently_loading = ''; } } - return plugin + return plugin; } } // Alias for typescript -export const BBPlugin = Plugin +export const BBPlugin = Plugin; if (isApp) { - Plugins.path = app.getPath('userData') + osfs + 'plugins' + osfs + Plugins.path = app.getPath('userData') + osfs + 'plugins' + osfs; fs.readdir(Plugins.path, function (err) { if (err) { - fs.mkdir(Plugins.path, function (a) {}) + fs.mkdir(Plugins.path, function (a) {}); } - }) + }); } else { - Plugins.path = Plugins.api_path + '/' + Plugins.path = Plugins.api_path + '/'; } Plugins.loading_promise = new Promise((resolve, reject) => { @@ -1047,45 +1050,45 @@ Plugins.loading_promise = new Promise((resolve, reject) => { url: Plugins.api_path + '.json', dataType: 'json', success(data) { - Plugins.json = data + Plugins.json = data; - resolve() - Plugins.loading_promise = null + resolve(); + Plugins.loading_promise = null; }, error() { - console.log('Could not connect to plugin server') - $('#plugin_available_empty').text('Could not connect to plugin server') - resolve() - Plugins.loading_promise = null + console.log('Could not connect to plugin server'); + $('#plugin_available_empty').text('Could not connect to plugin server'); + resolve(); + Plugins.loading_promise = null; if (settings.cdn_mirror.value == false && navigator.onLine) { - settings.cdn_mirror.set(true) - console.log('Switching to plugin CDN mirror. Restart to apply.') + settings.cdn_mirror.set(true); + console.log('Switching to plugin CDN mirror. Restart to apply.'); } }, - }) -}) + }); +}); $.getJSON('https://blckbn.ch/api/stats/plugins?weeks=2', data => { - Plugins.download_stats = data + Plugins.download_stats = data; if (Plugins.json) { - Plugins.sort() + Plugins.sort(); } -}) +}); export async function loadInstalledPlugins() { if (Plugins.loading_promise) { - await Plugins.loading_promise + await Plugins.loading_promise; } - const install_promises = [] - const online_access = Plugins.json instanceof Object && navigator.onLine + const install_promises = []; + const online_access = Plugins.json instanceof Object && navigator.onLine; // Setup offers from store if (online_access) { for (let id in Plugins.json) { - new Plugin(id, Plugins.json[id]) + new Plugin(id, Plugins.json[id]); } - Plugins.sort() + Plugins.sort(); } // Load plugins @@ -1097,68 +1100,68 @@ export async function loadInstalledPlugins() { console.error( `Could not resolve plugin dependencies: Recursive dependency on plugin "${installation.id}"`, installation - ) - return + ); + return; } for (let dependency_id of installation.dependencies) { let dependency_installation = Plugins.installed.find( inst => inst.id == dependency_id - ) - let this_index = Plugins.installed.indexOf(installation) - let dep_index = Plugins.installed.indexOf(dependency_installation) + ); + let this_index = Plugins.installed.indexOf(installation); + let dep_index = Plugins.installed.indexOf(dependency_installation); if (dependency_installation && dep_index > this_index) { - Plugins.installed.remove(dependency_installation) - Plugins.installed.splice(this_index, 0, dependency_installation) + Plugins.installed.remove(dependency_installation); + Plugins.installed.splice(this_index, 0, dependency_installation); if (dependency_installation.dependencies?.length) { - resolveDependencies(dependency_installation, depth + 1) + resolveDependencies(dependency_installation, depth + 1); } } } } for (let installation of Plugins.installed.slice()) { if (installation.dependencies?.length) { - resolveDependencies(installation, 0) + resolveDependencies(installation, 0); } } // Install plugins - var load_counter = 0 + var load_counter = 0; Plugins.installed.slice().forEach(function loadPlugin(installation) { if (installation.source == 'file') { // Dev Plugins if (isApp && fs.existsSync(installation.path)) { - var instance = new Plugin(installation.id, { disabled: installation.disabled }) + var instance = new Plugin(installation.id, { disabled: installation.disabled }); install_promises.push( instance.loadFromFile( { path: installation.path, name: installation.path, content: '' }, false ) - ) - load_counter++ + ); + load_counter++; console.log( `🧩📁 Loaded plugin "${installation.id || installation.path}" from file` - ) + ); } else { - Plugins.installed.remove(installation) + Plugins.installed.remove(installation); } } else if (installation.source == 'url') { // URL if (installation.path) { - var instance = new Plugin(installation.id, { disabled: installation.disabled }) - install_promises.push(instance.loadFromURL(installation.path, false)) - load_counter++ + var instance = new Plugin(installation.id, { disabled: installation.disabled }); + install_promises.push(instance.loadFromURL(installation.path, false)); + load_counter++; console.log( `🧩🌐 Loaded plugin "${installation.id || installation.path}" from URL` - ) + ); } else { - Plugins.installed.remove(installation) + Plugins.installed.remove(installation); } } else if (online_access) { // Store plugin - let plugin = Plugins.all.find(p => p.id == installation.id) + let plugin = Plugins.all.find(p => p.id == installation.id); if (plugin) { - plugin.installed = true - if (installation.disabled) plugin.disabled = true + plugin.installed = true; + if (installation.disabled) plugin.disabled = true; if ( isApp && @@ -1168,41 +1171,41 @@ export async function loadInstalledPlugins() { Blockbench.isOlderThan(plugin.min_version)) ) { // Get from file - let promise = plugin.load(false) - install_promises.push(promise) + let promise = plugin.load(false); + install_promises.push(promise); } else { // Update - let promise = plugin.download() + let promise = plugin.download(); if (plugin.await_loading) { - install_promises.push(promise) + install_promises.push(promise); } } - load_counter++ - console.log(`🧩🛒 Loaded plugin "${installation.id}" from store`) + load_counter++; + console.log(`🧩🛒 Loaded plugin "${installation.id}" from store`); } else if (Plugins.json instanceof Object && navigator.onLine) { - Plugins.installed.remove(installation) + Plugins.installed.remove(installation); } } else if (isApp && installation.source == 'store') { // Offline install store plugin - let plugin = new Plugin(installation.id) - let promise = plugin.load(false) - install_promises.push(promise) + let plugin = new Plugin(installation.id); + let promise = plugin.load(false); + install_promises.push(promise); } else { - Plugins.installed.remove(installation) + Plugins.installed.remove(installation); } - }) - console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`) + }); + console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`); } - StateMemory.save('installed_plugins') + StateMemory.save('installed_plugins'); install_promises.forEach(promise => { - promise.catch(console.error) - }) - return await Promise.allSettled(install_promises) + promise.catch(console.error); + }); + return await Promise.allSettled(install_promises); } BARS.defineActions(function () { - let actions_setup = false + let actions_setup = false; Plugins.dialog = new Dialog({ id: 'plugins', title: 'dialog.plugins.title', @@ -1211,11 +1214,11 @@ BARS.defineActions(function () { resizable: 'xy', onOpen() { if (!actions_setup) { - BarItems.load_plugin.toElement(document.getElementById('plugins_list_main_bar')) + BarItems.load_plugin.toElement(document.getElementById('plugins_list_main_bar')); BarItems.load_plugin_from_url.toElement( document.getElementById('plugins_list_main_bar') - ) - actions_setup = true + ); + actions_setup = true; } }, component: { @@ -1233,7 +1236,7 @@ BARS.defineActions(function () { }, computed: { plugin_search() { - let search_name = this.search_term.toUpperCase() + let search_name = this.search_term.toUpperCase(); if (search_name) { let filtered = this.items.filter(item => { return ( @@ -1242,23 +1245,23 @@ BARS.defineActions(function () { item.description.toUpperCase().includes(search_name) || item.author.toUpperCase().includes(search_name) || item.tags.find(tag => tag.toUpperCase().includes(search_name)) - ) - }) - let installed = filtered.filter(p => p.installed) - let not_installed = filtered.filter(p => !p.installed) - return installed.concat(not_installed) + ); + }); + let installed = filtered.filter(p => p.installed); + let not_installed = filtered.filter(p => !p.installed); + return installed.concat(not_installed); } else { return this.items.filter(item => { - return (this.tab == 'installed') == item.installed - }) + return (this.tab == 'installed') == item.installed; + }); } }, suggested_rows() { - let tags = ['Animation'] + let tags = ['Animation']; this.items.forEach(plugin => { - if (!plugin.installed) return - tags.safePush(...plugin.tags) - }) + if (!plugin.installed) return; + tags.safePush(...plugin.tags); + }); let rows = tags .map(tag => { let plugins = this.items @@ -1268,166 +1271,166 @@ BARS.defineActions(function () { plugin.tags.includes(tag) && !plugin.tags.includes('Deprecated') ) - .slice(0, 12) + .slice(0, 12); return { title: tag, plugins, - } + }; }) - .filter(row => row.plugins.length > 2) + .filter(row => row.plugins.length > 2); //rows.sort((a, b) => a.plugins.length - b.plugins.length); - rows.sort(() => Math.random() - 0.5) + rows.sort(() => Math.random() - 0.5); - let cutoff = Date.now() - 3_600_000 * 24 * 28 + let cutoff = Date.now() - 3_600_000 * 24 * 28; let new_plugins = this.items.filter( plugin => !plugin.installed && plugin.creation_date > cutoff && !plugin.tags.includes('Deprecated') - ) + ); if (new_plugins.length) { - new_plugins.sort((a, b) => a.creation_date - b.creation_date) + new_plugins.sort((a, b) => a.creation_date - b.creation_date); let new_row = { title: 'New', plugins: new_plugins.slice(0, 12), - } - rows.splice(0, 0, new_row) + }; + rows.splice(0, 0, new_row); } - return rows.slice(0, 3) + return rows.slice(0, 3); }, viewed_plugins() { return this.plugin_search.slice( this.page * this.per_page, (this.page + 1) * this.per_page - ) + ); }, pages() { - let pages = [] - let length = this.plugin_search.length + let pages = []; + let length = this.plugin_search.length; for (let i = 0; i * this.per_page < length; i++) { - pages.push(i) + pages.push(i); } - return pages + return pages; }, selected_plugin_settings() { - if (!this.selected_plugin) return {} - let plugin_settings = {} + if (!this.selected_plugin) return {}; + let plugin_settings = {}; for (let id in this.settings) { if (settings[id].plugin == this.selected_plugin.id) { - plugin_settings[id] = settings[id] + plugin_settings[id] = settings[id]; } } - return plugin_settings + return plugin_settings; }, }, methods: { setTab(tab) { - this.tab = tab - this.setPage(0) + this.tab = tab; + this.setPage(0); }, setPage(number) { - this.page = number - this.$refs.plugin_list.scrollTop = 0 + this.page = number; + this.$refs.plugin_list.scrollTop = 0; }, selectPlugin(plugin: Plugin) { if (!plugin) { - this.selected_plugin = Plugin.selected = null - return + this.selected_plugin = Plugin.selected = null; + return; } - plugin.fetchAbout() - this.selected_plugin = Plugin.selected = plugin + plugin.fetchAbout(); + this.selected_plugin = Plugin.selected = plugin; if (!this.selected_plugin.installed && this.page_tab == 'settings') { - this.page_tab == 'about' + this.page_tab == 'about'; } if (this.page_tab == 'changelog') { if (plugin.has_changelog) { - plugin.fetchChangelog() + plugin.fetchChangelog(); } else { - this.page_tab == 'about' + this.page_tab == 'about'; } } }, setPageTab(tab: string) { - this.page_tab = tab + this.page_tab = tab; if (this.page_tab == 'changelog' && this.selected_plugin.has_changelog) { - this.selected_plugin.fetchChangelog() + this.selected_plugin.fetchChangelog(); } }, showDependency(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency) + let plugin = Plugins.all.find(p => p.id == dependency); if (plugin) { - this.selectPlugin(plugin) + this.selectPlugin(plugin); } }, getDependencyName(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency) + let plugin = Plugins.all.find(p => p.id == dependency); return plugin ? plugin.title + (plugin.installed ? ' ✓' : '') - : dependency + ' ⚠' + : dependency + ' ⚠'; }, isDependencyInstalled(dependency: string) { - let plugin = Plugins.all.find(p => p.id == dependency) - return plugin && plugin.installed + let plugin = Plugins.all.find(p => p.id == dependency); + return plugin && plugin.installed; }, getTagClass(tag: string): string { if (tag.match(/^(local|remote)$/i)) { - return 'plugin_tag_source' + return 'plugin_tag_source'; } else if (tag.match(/^minecraft/i)) { - return 'plugin_tag_mc' + return 'plugin_tag_mc'; } else if (tag.match(/^deprecated/i)) { - return 'plugin_tag_deprecated' + return 'plugin_tag_deprecated'; } }, formatAbout(about: string) { - return pureMarked(about) + return pureMarked(about); }, reduceLink(url: string): string { - url = url.replace('https://', '').replace(/\/$/, '') + url = url.replace('https://', '').replace(/\/$/, ''); if (url.length > 50) { - return url.substring(0, 50) + '...' + return url.substring(0, 50) + '...'; } else { - return url + return url; } }, printDate(input_date: number) { - return getDateDisplay(input_date).short + return getDateDisplay(input_date).short; }, printDateFull(input_date: number) { - return getDateDisplay(input_date).full + return getDateDisplay(input_date).full; }, formatChangelogLine(line) { - let content = [] - let last_i = 0 + let content = []; + let last_i = 0; for (let match of line.matchAll(/\[.+?\]\(.+?\)/g)) { - let split = match[0].search(/\]\(/) - let label = match[0].substring(1, split) - let href = match[0].substring(split + 2, match[0].length - 1) - let a = Interface.createElement('a', { href, title: href }, label) - content.push(line.substring(last_i, match.index)) - content.push(a) - last_i = match.index + match[0].length + let split = match[0].search(/\]\(/); + let label = match[0].substring(1, split); + let href = match[0].substring(split + 2, match[0].length - 1); + let a = Interface.createElement('a', { href, title: href }, label); + content.push(line.substring(last_i, match.index)); + content.push(a); + last_i = match.index + match[0].length; } - content.push(line.substring(last_i)) + content.push(line.substring(last_i)); let node = Interface.createElement( 'p', {}, content.filter(a => a) - ) - return node.innerHTML + ); + return node.innerHTML; }, // Settings changePluginSetting(setting) { setTimeout(() => { if (typeof setting.onChange == 'function') { - setting.onChange(setting.value) + setting.onChange(setting.value); } - Settings.saveLocalStorages() - }, 20) + Settings.saveLocalStorages(); + }, 20); }, openSettingInSettings(key, profile) { - Settings.openDialog({ search_term: key, profile }) + Settings.openDialog({ search_term: key, profile }); }, settingContextMenu(setting, event) { new Menu([ @@ -1435,25 +1438,25 @@ BARS.defineActions(function () { name: 'dialog.settings.reset_to_default', icon: 'replay', click: () => { - setting.ui_value = setting.default_value - Settings.saveLocalStorages() + setting.ui_value = setting.default_value; + Settings.saveLocalStorages(); }, }, - ]).open(event) + ]).open(event); }, getProfileValuesForSetting(key) { return SettingsProfile.all.filter(profile => { - return profile.settings[key] !== undefined - }) + return profile.settings[key] !== undefined; + }); }, // Features getPluginFeatures(plugin) { - let types = [] + let types = []; - let formats = [] + let formats = []; for (let id in Formats) { - if (Formats[id].plugin == plugin.id) formats.push(Formats[id]) + if (Formats[id].plugin == plugin.id) formats.push(Formats[id]); } if (formats.length) { types.push({ @@ -1468,19 +1471,19 @@ BARS.defineActions(function () { click: format.show_on_start_screen && (() => { - Dialog.open.close() - StartScreen.open() - StartScreen.vue.loadFormat(format) + Dialog.open.close(); + StartScreen.open(); + StartScreen.vue.loadFormat(format); }), - } + }; }), - }) + }); } - let loaders = [] + let loaders = []; for (let id in ModelLoader.loaders) { if (ModelLoader.loaders[id].plugin == plugin.id) - loaders.push(ModelLoader.loaders[id]) + loaders.push(ModelLoader.loaders[id]); } if (loaders.length) { types.push({ @@ -1495,18 +1498,18 @@ BARS.defineActions(function () { click: loader.show_on_start_screen && (() => { - Dialog.open.close() - StartScreen.open() - StartScreen.vue.loadFormat(loader) + Dialog.open.close(); + StartScreen.open(); + StartScreen.vue.loadFormat(loader); }), - } + }; }), - }) + }); } - let codecs = [] + let codecs = []; for (let id in Codecs) { - if (Codecs[id].plugin == plugin.id) codecs.push(Codecs[id]) + if (Codecs[id].plugin == plugin.id) codecs.push(Codecs[id]); } if (codecs.length) { types.push({ @@ -1520,14 +1523,14 @@ BARS.defineActions(function () { description: codec.export_action ? codec.export_action.description : '', - } + }; }), - }) + }); } - let bar_items = Keybinds.actions.filter(action => action.plugin == plugin.id) - let tools = bar_items.filter(action => action instanceof Tool) - let other_actions = bar_items.filter(action => action instanceof Tool == false) + let bar_items = Keybinds.actions.filter(action => action.plugin == plugin.id); + let tools = bar_items.filter(action => action instanceof Tool); + let other_actions = bar_items.filter(action => action instanceof Tool == false); if (tools.length) { types.push({ @@ -1543,11 +1546,11 @@ BARS.defineActions(function () { click: Condition(tool.condition) && (() => { - ActionControl.select(tool.name) + ActionControl.select(tool.name); }), - } + }; }), - }) + }); } if (other_actions.length) { types.push({ @@ -1563,16 +1566,16 @@ BARS.defineActions(function () { click: Condition(action.condition) && (() => { - ActionControl.select(action.name) + ActionControl.select(action.name); }), - } + }; }), - }) + }); } - let panels = [] + let panels = []; for (let id in Panels) { - if (Panels[id].plugin == plugin.id) panels.push(Panels[id]) + if (Panels[id].plugin == plugin.id) panels.push(Panels[id]); } if (panels.length) { types.push({ @@ -1583,14 +1586,14 @@ BARS.defineActions(function () { id: panel.id, name: panel.name, icon: panel.icon, - } + }; }), - }) + }); } - let setting_list = [] + let setting_list = []; for (let id in settings) { - if (settings[id].plugin == plugin.id) setting_list.push(settings[id]) + if (settings[id].plugin == plugin.id) setting_list.push(settings[id]); } if (setting_list.length) { types.push({ @@ -1602,16 +1605,16 @@ BARS.defineActions(function () { name: setting.name, icon: setting.icon, click: () => { - this.page_tab = 'settings' + this.page_tab = 'settings'; }, - } + }; }), - }) + }); } let validator_checks = Validator.checks.filter( check => check.plugin == plugin.id - ) + ); if (validator_checks.length) { types.push({ id: 'validator_checks', @@ -1621,14 +1624,14 @@ BARS.defineActions(function () { id: validator_check.id, name: validator_check.name, icon: 'task_alt', - } + }; }), - }) + }); } //TODO //Modes //Element Types - return types + return types; }, getIconNode: Blockbench.getIconNode, @@ -1914,7 +1917,7 @@ BARS.defineActions(function () { `, }, - }) + }); new Action('plugins_window', { icon: 'extension', @@ -1922,29 +1925,29 @@ BARS.defineActions(function () { side_menu: new Menu('plugins_window', ['load_plugin', 'load_plugin_from_url']), click(e) { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin') - return + Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); + return; } - Plugins.dialog.show() - let none_installed = !Plugins.all.find(plugin => plugin.installed) - if (none_installed) Plugins.dialog.content_vue.tab = 'available' - $('dialog#plugins #plugin_search_bar input').trigger('focus') + Plugins.dialog.show(); + let none_installed = !Plugins.all.find(plugin => plugin.installed); + if (none_installed) Plugins.dialog.content_vue.tab = 'available'; + $('dialog#plugins #plugin_search_bar input').trigger('focus'); }, - }) + }); new Action('reload_plugins', { icon: 'sync', category: 'blockbench', click() { - Plugins.devReload() + Plugins.devReload(); }, - }) + }); new Action('load_plugin', { icon: 'fa-file-code', category: 'blockbench', click() { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin') - return + Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); + return; } Blockbench.import( { @@ -1953,42 +1956,42 @@ BARS.defineActions(function () { type: 'Blockbench Plugin', }, function (files) { - new Plugin().loadFromFile(files[0], true) + new Plugin().loadFromFile(files[0], true); } - ) + ); }, - }) + }); new Action('load_plugin_from_url', { icon: 'cloud_download', category: 'blockbench', click() { if (settings.classroom_mode.value) { - Blockbench.showQuickMessage('message.classroom_mode.install_plugin') - return + Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); + return; } Blockbench.textPrompt('URL', '', url => { - new Plugin().loadFromURL(url, true) - }) + new Plugin().loadFromURL(url, true); + }); }, - }) + }); new Action('add_plugin', { icon: 'add', category: 'blockbench', click() { - setTimeout(_ => ActionControl.select('+plugin: '), 1) + setTimeout(_ => ActionControl.select('+plugin: '), 1); }, - }) + }); new Action('remove_plugin', { icon: 'remove', category: 'blockbench', click() { - setTimeout(_ => ActionControl.select('-plugin: '), 1) + setTimeout(_ => ActionControl.select('-plugin: '), 1); }, - }) -}) + }); +}); Object.assign(window, { Plugins, Plugin, BBPlugin, -}) +}); diff --git a/js/shaders/shader.ts b/js/shaders/shader.ts index d851a0d51..5a918b404 100644 --- a/js/shaders/shader.ts +++ b/js/shaders/shader.ts @@ -1,4 +1,4 @@ -import { settings } from '../interface/settings' +import { settings } from '../interface/settings'; /** * Prepare shader with the correct options depending on device and settings @@ -9,10 +9,10 @@ export function prepareShader(shader: string): string { settings.antialiasing_bleed_fix.value == false || Preview.selected?.renderer.capabilities.isWebGL2 != true ) { - shader = shader.replace(/centroid /g, '') + shader = shader.replace(/centroid /g, ''); } if (!isApp) { - shader = shader.replace('precision highp', 'precision mediump') + shader = shader.replace('precision highp', 'precision mediump'); } - return shader + return shader; } diff --git a/js/texturing/ColorPickerNormal.vue b/js/texturing/ColorPickerNormal.vue index 36d07e26f..518d335fc 100644 --- a/js/texturing/ColorPickerNormal.vue +++ b/js/texturing/ColorPickerNormal.vue @@ -14,8 +14,8 @@