Merge branch 'next' into poly

This commit is contained in:
JannisX11 2021-08-06 20:41:45 +02:00
commit b193b61be3
33 changed files with 1234 additions and 721 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -8,6 +8,21 @@
opacity: 0.6;
z-index: 19;
}
.dialog_handle {
position: relative;
cursor: pointer;
overflow: hidden;
padding-left: 8px;
background: var(--color-button);
height: 30px;
flex: 0 0 auto;
}
.dialog_handle .dialog_title {
padding-top: 2px;
font-size: 1.12em;
padding-left: 16px;
pointer-events: none;
}
.dialog_close_button {
position: absolute;
right: 0px;
@ -21,6 +36,16 @@
color: var(--color-light);
background-color: var(--color-close);
}
.dialog_menu_button {
height: 100%;
width: 34px;
padding: 4px;
float: left;
text-align: center;
}
.dialog_menu_button:hover {
color: var(--color-light);
}
.dialog:not(.draggable) .dialog_close_button {
top: 8px;
right: -34px;
@ -41,7 +66,7 @@
bottom: unset;
display: none;
}
dialog > content {
dialog > content, dialog .dialog_wrapper > content {
display: block;
overflow-y: auto;
flex: 1 1 auto;
@ -72,11 +97,7 @@
}
.dialog:not(.ui-resizable) {
min-width: min(400px, 100%);
max-width: min(660px, 100%);
}
.dialog.paddinged {
padding: 24px;
padding-bottom: 12px;
max-width: min(960px, 100%);
}
.dialog_bar {
position: relative;
@ -102,7 +123,6 @@
}
.dialog_bar.button_bar {
text-align: right;
margin-top: 12px;
flex: 0 0 auto;
}
.dialog_bar button.large {
@ -155,12 +175,6 @@
color: var(--color-light);
}
.dialog .tab_content {
height: calc(100% - 90px);
width: 100%;
padding-bottom: 16px;
}
.dialog p {
margin: 4px 0;
}
@ -238,59 +252,129 @@
transition: color 750ms linear;
}
/* Sidebar */
.dialog_wrapper {
flex-grow: 1;
display: block;
}
.dialog_wrapper.has_sidebar {
display: grid;
grid-template-rows: auto 42px;
grid-template-columns: minmax(160px, 200px) auto;
grid-template-areas: "sidebar content" "sidebar buttons";
}
.dialog_sidebar {
background-color: var(--color-back);
flex: 1 0 160px;
position: relative;
grid-area: sidebar;
display: flex;
flex-direction: column;
}
.dialog_content {
display: block;
grid-area: content;
max-height: calc(100vh - 180px);
}
dialog .dialog_content,
dialog .dialog_bar.button_bar {
margin: 24px;
}
dialog .dialog_bar.button_bar {
grid-area: buttons;
margin-top: 0px;
margin-bottom: 12px;
}
.dialog_sidebar .dialog_sidebar_pages {
margin-top: 16px;
margin-bottom: 16px;
}
.dialog_sidebar .dialog_sidebar_pages li {
width: 100%;
padding: 4px 20px;
border-left: 4px solid transparent;
cursor: pointer;
}
.dialog_sidebar .dialog_sidebar_pages li:hover {
color: var(--color-light);
}
.dialog_sidebar .dialog_sidebar_pages li.selected {
background-color: var(--color-ui);
border-left: 4px solid var(--color-accent);
}
.dialog_sidebar .dialog_sidebar_actions {
bottom: 10px;
padding: 8px;
margin-top: auto;
border-top: 2px solid var(--color-border);
}
.dialog_sidebar .dialog_sidebar_actions li {
display: flex;
height: 30px;
padding: 4px;
padding-left: 34px;
padding-right: 8px;
cursor: pointer;
}
.dialog_sidebar .dialog_sidebar_actions li:hover {
color: var(--color-light);
}
.dialog_sidebar .dialog_sidebar_actions li i {
margin-top: 1px;
margin-right: 4px;
margin-left: -28px;
flex-shrink: 0;
pointer-events: none;
}
.dialog_sidebar .dialog_sidebar_actions li img {
cursor: default;
height: 20px;
width: 20px;
color: var(--color-text);
white-space: nowrap;
margin-bottom: -3px;
margin-left: -27px;
margin-right: 5px;
margin-top: 1px;
}
.dialog_sidebar .dialog_sidebar_actions li span {
pointer-events: none;
flex: 1 0 auto;
}
/*Settings Dialog*/
dialog#settings {
width: min(100%, 600px);
dialog#settings .dialog_wrapper {
min-height: 640px;
}
#settings_tab_bar {
margin: -24px;
margin-bottom: 0;
margin-top: -20px;
}
#settings h2 {
margin-top: 20px;
margin-bottom: 8px;
}
#settings h3 > i {
margin-top: 5px;
float: left;
}
#settings h3 > i.settings_expand_icon {
opacity: 0.7;
}
#settings h3:hover {
color: var(--color-light);
}
#settings .bar.next_to_title {
margin-top: -60px;
}
dialog#settings .search_bar {
margin-top: 26px;
dialog#settings h2, dialog#keybindings h2, dialog#theme h2 {
margin-top: -10px;
font-size: 1.8em;
}
/*Settings*/
#settings li h3 {
margin: 0;
padding: 0;
padding-top: 6px;
}
#settingslist {
width: 100%;
max-height: 600px;
overflow-y: scroll;
clear: both;
}
#settingslist li > ul {
margin-left: 24px;
}
#settingslist li li {
#settingslist li {
padding: 5px 0;
}
#settingslist li li:hover input[type=checkbox] {
#settingslist li:hover input[type=checkbox] {
color: var(--color-light);
}
#settingslist li > .setting_element {
#settingslist .setting_element {
width: 50px;
text-align: center;
float: left;
@ -337,12 +421,12 @@
#settingslist div.bar_select select {
width: 100%;
}
#settingslist li li .setting_icon i {
#settingslist li .setting_icon i {
font-size: 26pt;
width: 34px;
margin-top: -6px;
}
#settingslist li li:hover .setting_icon i {
#settingslist li:hover .setting_icon i {
color: var(--color-light);
}
.password_toggle {
@ -355,9 +439,12 @@
}
/*Keybinds*/
dialog#keybindings .dialog_wrapper {
min-height: 640px;
}
#keybindlist {
max-height: 600px;
padding-bottom: 25px;
margin-top: 16px;
overflow-y: scroll;
overflow-x: hidden;
clear: both;
@ -378,6 +465,7 @@
overflow: hidden;
white-space: nowrap;
display: inline-block;
cursor: text;
}
#keybindlist li > div.keybindslot:hover {
color: var(--color-light);
@ -394,17 +482,27 @@
vertical-align: top;
display: inline-block;
}
#keybindlist > li > ul > li {
#keybindlist > li {
position: relative;
display: flex;
}
#keybindlist > li {
width: 100%;
min-height: 34px;
padding-left: 6px;
}
.keybindslot .punctuation {
color: var(--color-subtle_text);
}
.keybindslot .modifier, .keybindslot .key {
background: var(--color-button);
padding: 2px 5px;
}
.keybindslot .optional {
color: var(--color-subtle_text);
padding: 2px 5px;
}
/*Colors*/
dialog#theme .dialog_wrapper {
min-height: 480px;
}
div#color_wrapper {
columns: 2;
margin-bottom: 20px;
@ -646,9 +744,10 @@
}
.dialog.draggable .bar.next_to_title {
width: max-content;
margin-top: -51px;
margin-top: -30px;
margin-left: 111px;
float: left;
z-index: inherit;
}
.dialog#plugins {
width: 100%;
@ -858,11 +957,37 @@
}
#bar_items_current li {
min-width: 20px;
cursor: move;
}
#bar_items_current li .toolbar_separator {
#bar_items_current li > * {
cursor: inherit;
}
#bar_items_current .toolbar_separator.border {
height: 32px;
width: 12px;
background-color: var(--color-button);
border: 1px solid var(--color-border);
background: var(--color-border);
}
#bar_items_current .toolbar_separator.spacer {
width: 40px;
}
#bar_items_current .toolbar_separator.spacer::after {
content: "";
border-bottom: 6px dotted var(--color-subtle_text);
display: block;
position: a;
height: 18px;
width: 32px;
}
#bar_items_current .toolbar_separator.linebreak {
height: 32px;
width: 20px;
background-color: var(--color-dark);
color: var(--color-subtle_text);
}
#bar_items_current .toolbar_separator.linebreak::after {
content: "¶";
font-size: 22px;
margin-left: 4px;
}
/*Action Control*/
@ -963,6 +1088,9 @@
#bar_item_list li {
padding: 4px;
}
#bar_item_list li.separator_item {
color: var(--color-accent);
}
#bar_item_list li:hover {
color: var(--color-light);
}

View File

@ -188,7 +188,7 @@
z-index: 100;
min-width: 150px;
max-width: 250px;
width: min-content;
width: fit-content;
padding: 0 12px;
background-color: var(--color-bright_ui);
color: var(--color-accent_text);
@ -360,7 +360,7 @@
height: 10px;
float: left;
}
.toolbar_separator {
.toolbar .toolbar_separator.border {
width: 2px;
height: 24px;
float: left;
@ -368,6 +368,17 @@
margin: 4px;
margin-bottom: 0;
}
.toolbar .toolbar_separator.spacer {
background: transparent;
flex-grow: 1;
}
.toolbar .toolbar_separator.linebreak {
display: block;
float: left;
width: 100%;
height: 0;
margin: 0;
}
.text_button:hover {
color: var(--color-light);
}

View File

@ -1,7 +1,7 @@
/*Layout*/
#page_wrapper {
height: calc(100% - 56px);
height: calc(100% - 26px);
width: 100%;
border: 2px solid var(--color-frame);
border-top: none;
@ -35,7 +35,7 @@
"left_bar center right_bar"
"left_bar status_bar right_bar";
width: 100%;
height: 100%;
height: calc(100% - 30px);
}
#tab_bar {

View File

@ -25,7 +25,7 @@
<script>
if (typeof module === 'object') {window.module = module; module = undefined;}//jQuery Fix
const isApp = typeof require !== 'undefined';
const appVersion = '3.9.2';
const appVersion = '4.0.0-beta.0';
window.onerror = (message, file, line) => {
if (typeof Blockbench != 'undefined' && Blockbench.setup_successful) return;
@ -144,40 +144,43 @@
</div>
</div>
<dialog class="dialog draggable paddinged" id="toolbar_edit">
<dialog class="dialog draggable" id="toolbar_edit">
<div class="dialog_handle tl">dialog.toolbar_edit.title</div>
<ul class="bar" id="bar_items_current" v-sortable="{onChoose: choose, onUpdate: sort, onEnd: drop, animation: 160 }">
<li v-for="item in currentBar" v-bind:title="item.name" :key="item.id||item">
<div v-if="typeof item === 'string'" class="toolbar_separator"></div>
<div v-else class="tool">
<div class="tooltip">{{item.name + (BARS.condition(item.condition) ? '' : ' (' + tl('dialog.toolbar_edit.hidden') + ')' )}}</div>
<span class="icon_wrapper" v-bind:style="{opacity: BARS.condition(item.condition) ? 1 : 0.4}" v-html="Blockbench.getIconNode(item.icon, item.color).outerHTML"></span>
</div>
</li>
</ul>
<div class="dialog_content">
<ul class="bar" id="bar_items_current" v-sortable="{onChoose: choose, onUpdate: sort, onEnd: drop, animation: 160 }">
<li v-for="item in currentBar" v-bind:title="item.name" :key="item.id||item">
<div v-if="typeof item === 'string'" class="toolbar_separator" :class="{border: item[0] == '_', spacer: item[0] == '+', linebreak: item[0] == '#'}"></div>
<div v-else class="tool">
<div class="tooltip">{{item.name + (BARS.condition(item.condition) ? '' : ' (' + tl('dialog.toolbar_edit.hidden') + ')' )}}</div>
<span class="icon_wrapper" v-bind:style="{opacity: BARS.condition(item.condition) ? 1 : 0.4}" v-html="Blockbench.getIconNode(item.icon, item.color).outerHTML"></span>
</div>
</li>
</ul>
<p class="tl small_text subtle">dialog.toolbar_edit.hidden_tools</p>
<p class="tl small_text subtle">dialog.toolbar_edit.hidden_tools</p>
<div class="bar" style="margin: 4px 0;">
<search-bar v-model="search_term"></search-bar>
</div>
<ul class="list" id="bar_item_list">
<li v-for="item in searchedBarItems" v-on:click="addItem(item)" :class="{separator_item: item.type == 'separator'}">
<div class="icon_wrapper normal" v-html="Blockbench.getIconNode(item.icon, item.color).outerHTML"></div>
<div class="icon_wrapper add"><i class="material-icons">add</i></div>
{{ item.name }}
</li>
</ul>
<div class="bar" style="margin: 4px 0;">
<search-bar v-model="search_term"></search-bar>
</div>
<ul class="list" id="bar_item_list">
<li v-for="item in searchedBarItems" v-on:click="addItem(item)">
<div class="icon_wrapper normal" v-html="Blockbench.getIconNode(item.icon, item.color).outerHTML"></div>
<div class="icon_wrapper add"><i class="material-icons">add</i></div>
{{ item.name }}
</li>
</ul>
<div class="dialog_bar button_bar">
<button type="button" class="cancel_btn confirm_btn uc_btn tl" onclick="hideDialog();">dialog.close</button>
</div>
<div class="dialog_close_button" onclick="hideDialog();"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="entity_import">
<dialog class="dialog draggable" id="entity_import">
<div class="dialog_handle tl">dialog.entitylist.title</div>
<div class="dialog_bar narrow tl">dialog.entitylist.text</div>
<div class="search_bar">
@ -200,7 +203,7 @@
<div class="dialog_close_button" onclick="hideDialog();BarItems.close_project.click()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="image_extruder">
<dialog class="dialog draggable" id="image_extruder">
<div class="dialog_handle tl">dialog.extrude.title</div>
<h1></h1>
@ -231,7 +234,7 @@
<div class="dialog_close_button" onclick="hideDialog()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="scaling">
<dialog class="dialog draggable" id="scaling">
<div class="dialog_handle tl">dialog.scale.title</div>
<label class="tl">dialog.scale.axis</label>
@ -279,7 +282,7 @@
<div class="dialog_close_button" onclick="cancelScaleAll()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="create_preset">
<dialog class="dialog draggable" id="create_preset">
<div class="dialog_handle tl">dialog.display_preset.title</div>
<div class="dialog_bar tl">dialog.display_preset.message</div>
<div class="dialog_bar">
@ -327,185 +330,6 @@
<div class="dialog_close_button" onclick="hideDialog()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="settings">
<div class="dialog_handle tl">menu.file.preferences</div>
<div class="dialog_bar borderless tab_bar" id="settings_tab_bar">
<div class="tl tab open" id="setting" onclick="setSettingsTab('setting')">dialog.settings.settings</div>
<div class="tl tab" id="keybindings" onclick="setSettingsTab('keybindings')">dialog.settings.keybinds</div>
<div class="tl tab" id="layout_settings" onclick="setSettingsTab('layout_settings')">dialog.settings.theme</div>
<div class="tl tab" id="credits" onclick="setSettingsTab('credits')">dialog.settings.about</div>
</div>
<div id="setting" class="tab_content">
<h2 class="tl i_b">dialog.settings.settings</h2>
<div class="bar next_to_title" id="settings_title_bar"></div>
<div class="search_bar">
<input type="text" class="dark_bordered" id="settings_search_bar" oninput="Settings.updateSearch()">
<i class="material-icons" id="settings_search_bar_icon">search</i>
</div>
<ul id="settingslist">
<li v-for="category in structure" v-if="!category.hidden">
<h3 v-on:click="toggleCategory(category)">
<i class="material-icons settings_expand_icon">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
{{ category.name }}
</h3>
<ul v-if="category.open">
<li v-for="(setting, key) in category.items" v-if="Condition(setting.condition)" v-on="setting.click ? {click: setting.click} : {}">
<template v-if="setting.type === 'number'">
<div class="setting_element"><input type="number" v-model.number="setting.value" :min="setting.min" :max="setting.max" :step="setting.step" v-on:input="saveSettings()"></div>
</template>
<template v-else-if="setting.type === 'click'">
<div class="setting_element setting_icon" v-html="Blockbench.getIconNode(setting.icon).outerHTML"></div>
</template>
<template v-else-if="setting.type == 'toggle'"><!--TOGGLE-->
<div class="setting_element"><input type="checkbox" v-model="setting.value" v-bind:id="'setting_'+key" v-on:click="saveSettings()"></div>
</template>
<label class="setting_label" v-bind:for="'setting_'+key">
<div class="setting_name">{{ setting.name }}</div>
<div class="setting_description">{{ setting.description }}</div>
</label>
<template v-if="setting.type === 'text'">
<input type="text" class="dark_bordered" style="width: 96%" v-model="setting.value" v-on:input="saveSettings()">
</template>
<template v-if="setting.type === 'password'">
<input :type="setting.hidden ? 'password' : 'text'" class="dark_bordered" style="width: calc(96% - 28px);" v-model="setting.value" v-on:input="saveSettings()">
<div class="password_toggle" @click="setting.hidden = !setting.hidden;">
<i class="fas fa-eye-slash" v-if="setting.hidden"></i>
<i class="fas fa-eye" v-else></i>
</div>
</template>
<template v-else-if="setting.type === 'select'">
<div class="bar_select">
<select v-model="setting.value">
<option v-for="(text, id) in setting.options" v-bind:value="id">{{ text }}</option>
</select>
</div>
</template>
</li>
</ul>
</li>
</ul>
</div>
<div id="keybindings" class="hidden tab_content">
<h2 class="tl i_b">dialog.settings.keybinds</h2>
<div class="bar next_to_title" id="keybinds_title_bar"></div>
<div class="search_bar">
<input type="text" class="dark_bordered" id="keybind_search_bar" oninput="Keybinds.updateSearch()">
<i class="material-icons" id="keybind_search_bar_icon">search</i>
</div>
<ul id="keybindlist">
<li v-for="category in structure" v-if="!category.hidden">
<h3 v-on:click="toggleCategory(category)">
<i class="material-icons f_left settings_expand_icon">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
{{ category.name }}
<i class="material-icons f_right" v-if="category.conflict" style="color: var(--color-close);">fiber_manual_record</i>
</h3>
<ul v-if="category.open">
<li v-for="action in category.actions">
<div v-bind:title="action.description">{{action.name}}</div>
<div class="keybindslot" :class="{conflict: action.keybind && action.keybind.conflict}" @click.stop="record(action)">{{ action.keybind ? action.keybind.label : '' }}</div>
<div class="tool" v-on:click="reset(action)">
<div class="tooltip tl">keybindings.reset</div>
<i class="material-icons">replay</i>
</div>
<div class="tool" v-on:click="clear(action)">
<div class="tooltip tl">keybindings.clear</div>
<i class="material-icons">clear</i>
</div>
</li>
</ul>
</li>
</ul>
</div>
<div id="layout_settings" class="hidden tab_content">
<h2 class="tl i_b">dialog.settings.theme</h2>
<div class="bar next_to_title" id="layout_title_bar"></div>
<div class="y_scrollable" id="theme_editor">
<div id="color_wrapper">
<div class="color_field" v-for="(color, key) in colors" :id="'color_field_' + key">
<div class="layout_color_preview" :style="{'background-color': color}" class="color_input"></div>
<div class="desc">
<h4>{{ tl('layout.color.'+key) }}</h4>
<p>{{ tl('layout.color.'+key+'.desc') }}</p>
</div>
</div>
</div>
<div class="dialog_bar">
<label class="name_space_left tl" for="layout_font_main">layout.font.main</label>
<input style="font-family: var(--font-main)" type="text" class="half dark_bordered" id="layout_font_main" v-model="main_font">
</div>
<div class="dialog_bar">
<label class="name_space_left tl" for="layout_font_headline">layout.font.headline</label>
<input style="font-family: var(--font-headline)" type="text" class="half dark_bordered" id="layout_font_headline" v-model="headline_font">
</div>
<div class="dialog_bar">
<label class="name_space_left tl" for="layout_font_cpde">layout.font.code</label>
<input style="font-family: var(--font-code)" type="text" class="half dark_bordered" id="layout_font_cpde" v-model="code_font">
</div>
<h4 class="tl i_b">layout.css</h4>
<div id="css_editor">
<vue-prism-editor v-model="css" language="css" :line-numbers="true" />
</div>
</div>
</div>
<div id="credits" class="hidden tab_content">
<h2 class="tl i_b">dialog.settings.about</h2>
<div id="about_page_title">
<img src="assets/logo_text_white.svg" width="240px">
</div>
<p><b class="tl">about.version</b> <span id="version_tag"></span></p>
<p><b class="tl">about.creator</b> JannisX11</p>
<p><b class="tl">about.website</b> <a class="open-in-browser" href="https://blockbench.net">blockbench.net</a></p>
<p><b class="tl">about.repository</b> <a class="open-in-browser" href="https://github.com/JannisX11/blockbench">github.com/JannisX11/blockbench</a></p>
<p class="local_only tl">about.electron</p>
<p class="tl">about.vertex_snap</p>
<p><b class="tl">about.icons</b> <a href="https://material.io/icons/" class="open-in-browser">material.io/icons</a> &amp; <a href="https://fontawesome.io/icons/" class="open-in-browser">fontawesome</a></p>
<p><b class="tl">about.libraries</b>
<a class="open-in-browser" href="https://vuejs.org">Vue</a>,
<a class="open-in-browser" href="https://github.com/weibangtuo/vue-tree">Vue Tree</a>,
<a class="open-in-browser" href="https://github.com/sagalbot/vue-sortable">Vue Sortable</a>,
<a class="open-in-browser" href="https://threejs.org">ThreeJS</a>,
<a class="open-in-browser" href="https://github.com/lo-th/fullik">Full IK</a>,
<a class="open-in-browser" href="https://github.com/oliver-moran/jimp">Jimp</a>,
<a class="open-in-browser" href="https://bgrins.github.io/spectrum">Spectrum</a>,
<a class="open-in-browser" href="https://github.com/jnordberg/gif.js">gif.js</a>,
<a class="open-in-browser" href="https://stuk.github.io/jszip/">JSZip</a>,
<a class="open-in-browser" href="https://github.com/rotemdan/lzutf8.js">LZ-UTF8</a>,
<a class="open-in-browser" href="https://jquery.com">jQuery</a>,
<a class="open-in-browser" href="https://jqueryui.com">jQuery UI</a>,
<a class="open-in-browser" href="https://github.com/furf/jquery-ui-touch-punch">jQuery UI Touch Punch</a>,
<a class="open-in-browser" href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a>,
<a class="open-in-browser" href="https://peerjs.com">PeerJS</a>,
<a class="open-in-browser" href="https://github.com/markedjs/marked">Marked</a>,
<a class="open-in-browser" href="https://prismjs.com">Prism</a>,
<a class="open-in-browser" href="https://github.com/koca/vue-prism-editor">Vue Prism Editor</a>,
<a class="open-in-browser" href="https://github.com/JannisX11/molangjs">MolangJS</a>,
<a class="open-in-browser" href="https://github.com/JannisX11/wintersky">Wintersky</a>
</p>
</div>
<div class="dialog_bar button_bar" hidden>
<button type="button" class="confirm_btn cancel_btn tl" onclick="Settings.save()">dialog.close</button>
</div>
<div class="dialog_close_button" onclick="Settings.save()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable" id="uv_dialog">
<div class="dialog_handle tl">uv_editor.title</div>
<div class="dialog_bar borderless tab_bar" id="uv_tab_bar">
@ -534,7 +358,7 @@
<div class="dialog_close_button" onclick="hideDialog()"><i class="material-icons">clear</i></div>
</dialog>
<dialog class="dialog draggable paddinged" id="text_input">
<dialog class="dialog draggable" id="text_input">
<div class="dialog_handle tl">dialog.input.title</div>
<div class="dialog_bar">

View File

@ -280,13 +280,13 @@ class Keyframe {
Timeline.dragging_keyframes = false
return this;
}
if (!event || (!event.shiftKey && !event.ctrlOrCmd)) {
if (!event || (!event.shiftKey && !event.ctrlOrCmd && !Pressing.overrides.ctrl && !Pressing.overrides.shift)) {
Timeline.selected.forEach(function(kf) {
kf.selected = false
})
Timeline.selected.empty()
}
if (event && event.shiftKey && Timeline.selected.length) {
if (event && (event.shiftKey || Pressing.overrides.shift) && Timeline.selected.length) {
var last = Timeline.selected[Timeline.selected.length-1]
if (last && last.channel === scope.channel && last.animator == scope.animator) {
Timeline.keyframes.forEach((kf) => {
@ -412,6 +412,7 @@ class Keyframe {
])
new Property(Keyframe, 'number', 'time')
new Property(Keyframe, 'number', 'color', {default: -1})
new Property(Keyframe, 'boolean', 'uniform', {condition: keyframe => keyframe.channel == 'scale', default: true})
new Property(Keyframe, 'string', 'interpolation', {default: 'linear'})
Keyframe.selected = [];
Keyframe.interpolation = {
@ -490,8 +491,8 @@ BARS.defineActions(function() {
if (Toolbox.selected.id == 'rotate_tool' && animator.channels.includes('rotation')) channel = 'rotation';
if (Toolbox.selected.id == 'move_tool' && animator.channels.includes('position')) channel = 'position';
if (Toolbox.selected.id == 'resize_tool' && animator.channels.includes('scale')) channel = 'scale';
animator.createKeyframe((event && event.shiftKey) ? {} : null, Timeline.time, channel, true);
if (event && event.shiftKey) {
animator.createKeyframe((event && (event.shiftKey || Pressing.overrides.shift)) ? {} : null, Timeline.time, channel, true);
if (event && (event.shiftKey || Pressing.overrides.shift)) {
Animator.preview();
}
}

View File

@ -77,7 +77,7 @@ const Timeline = {
e.clientX - R.panel_offset[0],
e.clientY - R.panel_offset[1],
]
if (e.shiftKey) {
if (e.shiftKey || Pressing.overrides.shift) {
Timeline.selector.selected_before = Timeline.selected.slice();
}
R.selecting = true;
@ -268,7 +268,7 @@ const Timeline = {
let offset = e.clientX - $('#timeline_time').offset().left;
let time = Math.clamp(offset / Timeline.vue._data.size, 0, Infinity);
if (!e.ctrlOrCmd) time = Timeline.snapTime(time);
if (!e && !Pressing.overrides.ctrl) time = Timeline.snapTime(time);
Timeline.setTime(time);
Animator.preview();
}
@ -279,7 +279,7 @@ const Timeline = {
convertTouchEvent(e);
let offset = e.clientX - $('#timeline_time').offset().left;
let time = Math.clamp(offset / Timeline.vue._data.size, 0, Infinity);
if (!e.ctrlOrCmd) time = Timeline.snapTime(time);
if (!e.ctrlOrCmd && !Pressing.overrides.ctrl) time = Timeline.snapTime(time);
if (Timeline.time != time) {
Timeline.setTime(time)
Animator.preview()
@ -750,10 +750,10 @@ onVueSetup(function() {
dragging_restriction;
originalValue;
previousValue = 0;
time_stretching = !Timeline.vue.graph_editor_open && e1.ctrlOrCmd && Timeline.selected.length > 1;
time_stretching = !Timeline.vue.graph_editor_open && (e1.ctrlOrCmd || Pressing.overrides.ctrl) && Timeline.selected.length > 1;
values_changed = false;
if (!clicked.selected && !e1.shiftKey && Timeline.selected.length != 0) {
if (!clicked.selected && !e1.shiftKey && !Pressing.overrides.shift && Timeline.selected.length != 0) {
clicked.select()
} else if (clicked && !clicked.selected) {
clicked.select({shiftKey: true})
@ -824,7 +824,7 @@ onVueSetup(function() {
let value_diff = 0;
if (Timeline.vue.graph_editor_open) {
value = -offset[1] / Timeline.vue.graph_size;
var round_num = canvasGridSize(e2.shiftKey, e2.ctrlOrCmd);
var round_num = canvasGridSize(e2.shiftKey || Pressing.overrides.shift, e2.ctrlOrCmd || Pressing.overrides.ctrl);
if (Toolbox.selected.id === 'resize_tool') {
round_num *= 0.1;
}

View File

@ -212,7 +212,7 @@ const Blockbench = {
}
var jq_dialog = $(`
<dialog class="dialog paddinged" style="width: auto;" id="message_box">
<dialog class="dialog" style="width: auto;" id="message_box">
<div class="dialog_handle">${tl(options.title)}</div>
<div class="dialog_close_button" onclick="open_interface.cancel()"><i class="material-icons">clear</i></div>
</dialog>`)

View File

@ -36,12 +36,6 @@ var Prop = {
const mouse_pos = {x:0,y:0}
const sort_collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
function onVueSetup(func) {
if (!onVueSetup.funcs) {
onVueSetup.funcs = []
}
onVueSetup.funcs.push(func)
}
function canvasGridSize(shift, ctrl) {
if (!shift && !ctrl) {
return 16 / Math.clamp(settings.edit_size.value, 1, 512)
@ -232,6 +226,89 @@ const documentReady = new Promise((resolve, reject) => {
})
});
BARS.defineActions(() => {
new Action('about_window', {
name: tl('dialog.settings.about') + '...',
icon: 'info',
category: 'blockbench',
click: function () {
const data = {
isApp,
version_label: Blockbench.version
};
jQuery.ajax({
url: 'https://api.github.com/repos/JannisX11/blockbench/releases/latest',
cache: false,
type: 'GET',
success(release) {
let v = release.tag_name.replace(/^v/, '');
if (compareVersions(v, Blockbench.version)) {
data.version_label = `${Blockbench.version} (${tl('about.version.update_available', [v])})`;
} else if (compareVersions(Blockbench.version, v)) {
data.version_label = `${Blockbench.version} (Pre-release)`;
} else {
data.version_label = `${Blockbench.version} (${tl('about.version.up_to_date')}😄)`;
}
},
error(err) {}
})
new Dialog({
id: 'about',
title: 'dialog.settings.about',
width: 600,
singleButton: true,
title_menu: new Menu([
'settings_window',
'keybindings_window',
'theme_window',
'about_window',
]),
component: {
data() {return data},
template: `
<div>
<div id="about_page_title">
<img src="assets/logo_text_white.svg" width="240px">
</div>
<p><b>${tl('about.version')}</b> <span>{{ version_label }}</span></p>
<p><b>${tl('about.creator')}</b> JannisX11</p>
<p><b>${tl('about.website')}</b> <a class="open-in-browser" href="https://blockbench.net">blockbench.net</a></p>
<p><b>${tl('about.repository')}</b> <a class="open-in-browser" href="https://github.com/JannisX11/blockbench">github.com/JannisX11/blockbench</a></p>
<p>${tl('about.vertex_snap')}</p>
<p><b>${tl('about.icons')}</b> <a href="https://material.io/icons/" class="open-in-browser">material.io/icons</a> &amp; <a href="https://fontawesome.io/icons/" class="open-in-browser">fontawesome</a></p>
<p><b>${tl('about.libraries')}</b>
<a class="open-in-browser" href="https://electronjs.org">Electron</a>,
<a class="open-in-browser" href="https://vuejs.org">Vue</a>,
<a class="open-in-browser" href="https://github.com/weibangtuo/vue-tree">Vue Tree</a>,
<a class="open-in-browser" href="https://github.com/sagalbot/vue-sortable">Vue Sortable</a>,
<a class="open-in-browser" href="https://threejs.org">ThreeJS</a>,
<a class="open-in-browser" href="https://github.com/lo-th/fullik">Full IK</a>,
<a class="open-in-browser" href="https://github.com/oliver-moran/jimp">Jimp</a>,
<a class="open-in-browser" href="https://bgrins.github.io/spectrum">Spectrum</a>,
<a class="open-in-browser" href="https://github.com/jnordberg/gif.js">gif.js</a>,
<a class="open-in-browser" href="https://stuk.github.io/jszip/">JSZip</a>,
<a class="open-in-browser" href="https://github.com/rotemdan/lzutf8.js">LZ-UTF8</a>,
<a class="open-in-browser" href="https://jquery.com">jQuery</a>,
<a class="open-in-browser" href="https://jqueryui.com">jQuery UI</a>,
<a class="open-in-browser" href="https://github.com/furf/jquery-ui-touch-punch">jQuery UI Touch Punch</a>,
<a class="open-in-browser" href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a>,
<a class="open-in-browser" href="https://peerjs.com">PeerJS</a>,
<a class="open-in-browser" href="https://github.com/markedjs/marked">Marked</a>,
<a class="open-in-browser" href="https://prismjs.com">Prism</a>,
<a class="open-in-browser" href="https://github.com/koca/vue-prism-editor">Vue Prism Editor</a>,
<a class="open-in-browser" href="https://github.com/JannisX11/molangjs">MolangJS</a>,
<a class="open-in-browser" href="https://github.com/JannisX11/wintersky">Wintersky</a>
</p>
</div>`
}
}).show()
}
})
})
const entityMode = {
hardcodes: JSON.parse('{"geometry.chicken":{"body":{"rotation":[90,0,0]}},"geometry.llama":{"chest1":{"rotation":[0,90,0]},"chest2":{"rotation":[0,90,0]},"body":{"rotation":[90,0,0]}},"geometry.cow":{"body":{"rotation":[90,0,0]}},"geometry.sheep.sheared":{"body":{"rotation":[90,0,0]}},"geometry.sheep":{"body":{"rotation":[90,0,0]}},"geometry.phantom":{"body":{"rotation":[0,0,0]},"wing0":{"rotation":[0,0,5.7]},"wingtip0":{"rotation":[0,0,5.7]},"wing1":{"rotation":[0,0,-5.7]},"wingtip1":{"rotation":[0,0,-5.7]},"head":{"rotation":[11.5,0,0]},"tail":{"rotation":[0,0,0]},"tailtip":{"rotation":[0,0,0]}},"geometry.pig":{"body":{"rotation":[90,0,0]}},"geometry.ocelot":{"body":{"rotation":[90,0,0]},"tail1":{"rotation":[90,0,0]},"tail2":{"rotation":[90,0,0]}},"geometry.cat":{"body":{"rotation":[90,0,0]},"tail1":{"rotation":[90,0,0]},"tail2":{"rotation":[90,0,0]}},"geometry.turtle":{"eggbelly":{"rotation":[90,0,0]},"body":{"rotation":[90,0,0]}},"geometry.villager.witch":{"hat2":{"rotation":[-3,0,1.5]},"hat3":{"rotation":[-6,0,3]},"hat4":{"rotation":[-12,0,6]}},"geometry.pufferfish.mid":{"spines_top_front":{"rotation":[45,0,0]},"spines_top_back":{"rotation":[-45,0,0]},"spines_bottom_front":{"rotation":[-45,0,0]},"spines_bottom_back":{"rotation":[45,0,0]},"spines_left_front":{"rotation":[0,45,0]},"spines_left_back":{"rotation":[0,-45,0]},"spines_right_front":{"rotation":[0,-45,0]},"spines_right_back":{"rotation":[0,45,0]}},"geometry.pufferfish.large":{"spines_top_front":{"rotation":[45,0,0]},"spines_top_back":{"rotation":[-45,0,0]},"spines_bottom_front":{"rotation":[-45,0,0]},"spines_bottom_back":{"rotation":[45,0,0]},"spines_left_front":{"rotation":[0,45,0]},"spines_left_back":{"rotation":[0,-45,0]},"spines_right_front":{"rotation":[0,-45,0]},"spines_right_back":{"rotation":[0,45,0]}},"geometry.tropicalfish_a":{"leftFin":{"rotation":[0,-35,0]},"rightFin":{"rotation":[0,35,0]}},"geometry.tropicalfish_b":{"leftFin":{"rotation":[0,-35,0]},"rightFin":{"rotation":[0,35,0]}}}')
}

View File

@ -1059,7 +1059,7 @@ class Toolbar {
this.condition_cache = [];
// items the toolbar could not load on startup, most likely from plugins (stored as IDs)
this.postload = undefined;
this.postload = null;
// object storing initial position of actions
// if a property with a given position is set, then this slot is occupied
// and the associated object (action) can effectively be used with indexOf on children
@ -1100,9 +1100,14 @@ class Toolbar {
content.children().detach()
for (var itemPosition = 0; itemPosition < items.length; itemPosition++) {
var itemId = items[itemPosition];
if (typeof itemId === 'string' && itemId.substr(0, 1) === '_') {
content.append('<div class="toolbar_separator"></div>');
this.children.push('_' + guid().substr(0,8));
if (typeof itemId === 'string' && itemId.match(/^[_+#]/)) {
let char = itemId.substr(0, 1);
content.append(`<div class="toolbar_separator ${char == '_' ? 'border' : (char == '+' ? 'spacer' : 'linebreak')}"></div>`);
this.children.push(char + guid().substr(0,8));
if (char === '+') content[0].style.display = 'flex';
if (char === '#') content[0].style.display = 'block';
continue;
}
@ -1201,7 +1206,7 @@ class Toolbar {
}
}
if (this.postload.length == 0) {
this.postload = undefined; // array obj no longer needed
this.postload = null; // array obj no longer needed
}
}
@ -1223,8 +1228,11 @@ class Toolbar {
var content = $(this.node).find('.content')
content.find('> .tool').detach()
var separators = content.find('> .toolbar_separator').detach()
var sep_nr = 0;
var separators = {
border: content.find('> .toolbar_separator.border').detach().toArray(),
spacer: content.find('> .toolbar_separator.spacer').detach().toArray(),
linebreak: content.find('> .toolbar_separator.linebreak').detach().toArray(),
}
this.children.forEach(function(item, i) {
if (typeof item === 'string') {
@ -1232,13 +1240,18 @@ class Toolbar {
if (last.length === 0 || last.hasClass('toolbar_separator') || i == scope.children.length-1) {
return this;
}
var sep = separators[sep_nr]
let type = item[0] == '_' ? 'border' : (item[0] == '+' ? 'spacer' : 'linebreak');
let sep = separators[type].shift();
if (sep) {
content.append(sep)
sep_nr++;
content.append(sep);
} else {
content.append('<div class="toolbar_separator"></div>')
let separator = document.createElement('div');
separator.className = `toolbar_separator ${type}`;
content.append(separator);
}
if (item[0] === '+') content[0].style.display = 'flex';
if (item[0] === '#') content[0].style.display = 'block';
} else if (typeof item === 'object') {
if (scope.condition_cache[i]) {
content.append(item.getNode())
@ -1320,6 +1333,10 @@ const BARS = {
category: 'navigate',
keybind: new Keybind({key: 1, shift: true})
})
new KeybindItem('preview_area_select', {
category: 'navigate',
keybind: new Keybind({key: 1, ctrl: true, shift: null})
})
new KeybindItem('confirm', {
category: 'navigate',
@ -1431,23 +1448,6 @@ const BARS = {
shell.openPath(app.getPath('userData')+osfs+'backups')
}
})
new Action('settings_window', {
icon: 'settings',
category: 'blockbench',
click: function () {Settings.open()}
})
new Action('keybindings_window', {
name: tl('dialog.settings.keybinds') + '...',
icon: 'keyboard',
category: 'blockbench',
click: function () {Settings.open({tab: 'keybindings'})}
})
new Action('theme_window', {
name: tl('dialog.settings.theme') + '...',
icon: 'style',
category: 'blockbench',
click: function () {Settings.open({tab: 'layout_settings'})}
})
new Action('reload', {
icon: 'refresh',
category: 'file',
@ -1913,11 +1913,26 @@ const BARS = {
computed: {
searchedBarItems() {
var name = this.search_term.toUpperCase()
var list = [{
icon: 'bookmark',
name: tl('data.separator'),
type: 'separator'
}]
var list = [
{
icon: 'fa-grip-lines-vertical',
name: tl('data.separator'),
type: 'separator',
separator_code: '_'
},
{
icon: 'space_bar',
name: tl('data.separator.spacer'),
type: 'separator',
separator_code: '+'
},
{
icon: 'fa-paragraph',
name: tl('data.separator.linebreak'),
type: 'separator',
separator_code: '#'
}
]
for (var key in BarItems) {
var item = BarItems[key]
if (name.length == 0 ||
@ -1935,35 +1950,39 @@ const BARS = {
}
},
methods: {
sort: function(event) {
sort(event) {
var item = this.currentBar.splice(event.oldIndex, 1)[0]
this.currentBar.splice(event.newIndex, 0, item)
this.update();
},
drop: function(event) {
drop(event) {
var scope = this;
var index = event.oldIndex
$('#bar_items_current .tooltip').css('display', '')
setTimeout(() => {
if ($('#bar_items_current:hover').length === 0) {
var item = scope.currentBar.splice(event.oldIndex, 1)[0]
item.toolbars.remove(BARS.editing_bar)
scope.update()
var item = scope.currentBar.splice(event.newIndex, 1)[0];
if (item instanceof BarItem) item.toolbars.remove(BARS.editing_bar);
scope.update();
}
}, 30)
},
choose: function(event) {
choose() {
$('#bar_items_current .tooltip').css('display', 'none')
},
update: function() {
update() {
BARS.editing_bar.update(true).save();
},
addItem: function(item) {
addItem(item) {
if (item.type === 'separator') {
item = '_'
item = item.separator_code;
}
if (item == '#' && BARS.editing_bar.children.find(c => typeof c == 'string' && c[0] == '+') ||
item == '+' && BARS.editing_bar.children.find(c => typeof c == 'string' && c[0] == '#')
) {
Blockbench.showQuickMessage('dialog.toolbar_edit.incompatible_separators', 2000);
} else {
BARS.editing_bar.add(item);
}
BARS.editing_bar.add(item);
// BARS.editing_bar.update().save(); already called in add()
}
}
})
@ -2145,14 +2164,7 @@ const Keybinds = {
actions: [],
stored: {},
extra: {},
structure: {
search_results: {
name: tl('dialog.settings.search_results'),
hidden: true,
open: true,
actions: {}
}
},
structure: {},
save() {
localStorage.setItem('keybindings', JSON.stringify(Keybinds.stored))
}

View File

@ -294,6 +294,85 @@ function buildComponent(dialog) {
dialog.content_vue = new Vue(dialog.component).$mount(mount.get(0));
}
class DialogSidebar {
constructor(options) {
this.open = true;
this.pages = options.pages || {};
this.page = options.page || Object.keys(this.pages)[0];
this.actions = options.actions || {};
this.onPageSwitch = options.onPageSwitch || null;
}
build() {
if (!this.node) {
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 = {};
for (let key in this.pages) {
let li = document.createElement('li');
li.textContent = this.pages[key];
if (this.page == key) li.classList.add('selected');
this.page_menu[key] = li;
li.addEventListener('click', event => {
this.setPage(key);
})
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);
this.actions.forEach(action => {
if (typeof action == 'string') {
action = BarItems[action];
}
let copy;
if (action instanceof Action) {
copy = action.menu_node.cloneNode(true);
copy.addEventListener('click', 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.addEventListener('click', event => {
Blockbench.openLink('https://www.blockbench.net/wiki/blockbench/themes');
})
}
action_list.append(copy);
})
}
this.toggle(this.open);
return this.node;
}
toggle(state = !this.open) {
this.open = state;
this.node.style.display = this.open ? 'flex' : 'none';
if (this.node.parentElement) {
this.node.parentElement.classList.toggle('has_sidebar', this.open);
}
}
setPage(page) {
this.page = page;
if (this.onPageSwitch) this.onPageSwitch(page);
for (let key in this.page_menu) {
let li = this.page_menu[key];
li.classList.toggle('selected', key == this.page);
}
}
}
window.Dialog = class Dialog {
constructor(id, options) {
@ -309,8 +388,10 @@ window.Dialog = class Dialog {
this.component = options.component
this.part_order = options.part_order || (options.form_first ? ['form', 'lines', 'component'] : ['lines', 'form', 'component'])
this.sidebar = options.sidebar ? new DialogSidebar(options.sidebar) : null;
this.title_menu = options.title_menu || null;
this.width = options.width
this.padding = options.padding != false;
this.draggable = options.draggable
this.singleButton = options.singleButton
this.buttons = options.buttons instanceof Array ? options.buttons : (options.singleButton ? ['dialog.close'] : ['dialog.confirm', 'dialog.cancel'])
@ -409,14 +490,45 @@ window.Dialog = class Dialog {
this.hide();
}
build() {
var jq_dialog = $(`<dialog class="dialog" id="${this.id}">
<div class="dialog_handle">${tl(this.title)}</div>
<content class="dialog_content"></content>
</dialog>`)
this.object = jq_dialog.get(0)
this.max_label_width = 0;
if (this.padding) this.object.classList.add('paddinged');
this.object = document.createElement('dialog');
this.object.className = 'dialog';
this.object.id = this.id;
let handle = document.createElement('div');
handle.className = 'dialog_handle';
if (this.title_menu) {
let menu_button = document.createElement('div');
menu_button.className = 'dialog_menu_button';
menu_button.append(Blockbench.getIconNode('menu'));
menu_button.addEventListener('click', event => {
this.title_menu.show(menu_button);
})
handle.append(menu_button);
}
let title = document.createElement('div');
title.className = 'dialog_title';
title.textContent = tl(this.title);
handle.append(title);
this.object.append(handle);
let jq_dialog = $(this.object);
this.max_label_width = 0;
let wrapper = document.createElement('div');
wrapper.className = 'dialog_wrapper';
let content = document.createElement('content');
content.className = 'dialog_content';
this.object.append(wrapper);
if (this.sidebar) {
let sidebar = this.sidebar.build();
wrapper.append(sidebar);
wrapper.classList.toggle('has_sidebar', this.sidebar.open);
}
wrapper.append(content);
this.part_order.forEach(part => {
if (part == 'form' && this.form) buildForm(this);
@ -440,13 +552,14 @@ window.Dialog = class Dialog {
})
buttons[this.confirmIndex] && buttons[this.confirmIndex].addClass('confirm_btn')
buttons[this.cancelIndex] && buttons[this.cancelIndex].addClass('cancel_btn')
let bar = $('<div class="dialog_bar button_bar"></div>');
jq_dialog.append(bar);
let button_bar = $('<div class="dialog_bar button_bar"></div>');
buttons.forEach((button, i) => {
if (i) bar.append('&nbsp;')
bar.append(button)
if (i) button_bar.append('&nbsp;')
button_bar.append(button)
})
wrapper.append(button_bar[0]);
}
let close_button = document.createElement('div');

View File

@ -508,51 +508,6 @@ function hideDialog() {
open_interface = false;
Prop.active_panel = undefined
}
function setSettingsTab(tab) {
$('#settings .tab.open').removeClass('open')
$('#settings .tab#'+tab).addClass('open')
$('#settings .tab_content').addClass('hidden')
$('#settings .tab_content#'+tab).removeClass('hidden')
if (tab === 'keybindings') {
//Keybinds
$('#keybindlist').css('max-height', (window.innerHeight - 420) +'px')
$('#keybind_search_bar').focus()
} else if (tab === 'setting') {
//Settings
$('#settingslist').css('max-height', (window.innerHeight - 420) +'px')
$('#settings_search_bar').focus()
} else if (tab === 'layout_settings') {
//Layout
$('#theme_editor').css('max-height', (window.innerHeight - 420) +'px')
if (!CustomTheme.dialog_is_setup) CustomTheme.setupDialog()
} else if (tab == 'credits') {
// About
$('#version_tag').text(appVersion)
if (isApp) {
jQuery.ajax({
url: 'https://api.github.com/repos/JannisX11/blockbench/releases/latest',
cache: false,
type: 'GET',
success(release) {
let v = release.tag_name.replace(/^v/, '');
if (compareVersions(v, appVersion)) {
$('#version_tag').text(`${appVersion} (${tl('about.version.update_available', [v])})`)
} else if (compareVersions(appVersion, v)) {
$('#version_tag').text(`${appVersion} (Pre-release)`)
} else {
$('#version_tag').text(`${appVersion} (${tl('about.version.up_to_date')}😄)`)
}
},
error(err) {
}
})
}
}
}
function getStringWidth(string, size) {
var a = $('<label style="position: absolute">'+string+'</label>')

View File

@ -88,7 +88,7 @@ class Keybind {
Keybinds.structure[action.category].actions.push(action)
return this;
}
getText() {
getText(colorized = false) {
if (this.key < 0) return '';
var modifiers = []
@ -108,7 +108,18 @@ class Keybind {
} else {
modifiers.push(char_tl)
}
return modifiers.join(' + ')
if (colorized) {
modifiers.forEach((text, i) => {
let type = i !== modifiers.length-1
? text.match(/\[\w+\]/) ? 'optional' : 'modifier'
: 'key'
modifiers[i] = `<span class="${type}">${text}</span>`;
})
return modifiers.join(`<span class="punctuation"> + </span>`);
} else {
return modifiers.join(' + ');
}
}
getCode(key) {
if (!key) key = this.key;
@ -174,11 +185,11 @@ class Keybind {
}
isTriggered(event) {
return (
(this.key === event.which || (this.key == 1001 && event instanceof MouseEvent)) &&
(this.ctrl === event.ctrlKey || this.ctrl == null ) &&
(this.shift === event.shiftKey || this.shift == null ) &&
(this.alt === event.altKey || this.alt == null ) &&
(this.meta === event.metaKey || this.meta == null )
(this.key === event.which || (this.key == 1001 && event instanceof MouseEvent)) &&
(this.ctrl === (event.ctrlKey || Pressing.overrides.ctrl) || this.ctrl == null ) &&
(this.shift === (event.shiftKey || Pressing.overrides.shift)|| this.shift == null ) &&
(this.alt === (event.altKey || Pressing.overrides.alt) || this.alt == null ) &&
(this.meta === event.metaKey || this.meta == null )
)
}
record() {
@ -282,6 +293,7 @@ Keybinds.loadKeymap = function(id, from_start_screen = false) {
Keybinds.extra.preview_rotate.keybind.set({key: 2}).save(false);
Keybinds.extra.preview_drag.keybind.set({key: 2, shift: true}).save(false);
Keybinds.extra.preview_zoom.keybind.set({key: 2, ctrl: true}).save(false);
Keybinds.extra.preview_area_select.keybind.set({key: 1}).save(false);
}
Keybinds.save();
@ -324,88 +336,17 @@ function updateKeybindConflicts() {
})
}
onVueSetup(function() {
Keybinds.vue = new Vue({
el: 'ul#keybindlist',
data: {structure: Keybinds.structure},
methods: {
},
methods: {
record(item) {
if (!item.keybind) {
item.keybind = new Keybind()
}
item.keybind.record()
},
reset(item) {
if (item.keybind) {
if (item.default_keybind) {
item.keybind.set(item.default_keybind);
} else {
item.keybind.clear();
}
item.keybind.save(true);
}
},
clear(item) {
if (item.keybind) {
item.keybind.clear().save(true)
}
},
toggleCategory(category) {
if (!category.open) {
for (var ct in Keybinds.structure) {
Keybinds.structure[ct].open = false
}
}
category.open = !category.open
}
}
})
Keybinds.updateSearch = function() {
var term = Keybinds.vue._data.search_term = $('input#keybind_search_bar').val().toLowerCase();
var structure = Keybinds.structure;
if (term) {
var keywords = term.replace(/_/g, ' ').split(' ');
var actions = [];
for (var action of Keybinds.actions) {
if (true) {;
var missmatch = false;
for (var word of keywords) {
if (
!action.name.toLowerCase().includes(word) &&
!action.id.toLowerCase().includes(word) &&
!action.keybind.label.toLowerCase().includes(word)
) {
missmatch = true;
}
}
if (!missmatch) {
actions.push(action)
}
}
}
structure.search_results.actions = actions
structure.search_results.hidden = false;
for (var key in structure) {
structure[key].open = false
}
structure.search_results.open = true;
} else {
structure.search_results.hidden = true;
}
}
})
BARS.defineActions(() => {
new Action('keybindings_window', {
name: tl('dialog.settings.keybinds') + '...',
icon: 'keyboard',
category: 'blockbench',
click: function () {
Keybinds.dialog.show();
}
})
new Action('load_keymap', {
icon: 'format_list_bulleted',
category: 'blockbench',
@ -474,6 +415,138 @@ BARS.defineActions(() => {
BarItems.export_keymap.toElement('#keybinds_title_bar')
})
onVueSetup(function() {
let sidebar_pages = {};
for (let key in Keybinds.structure) {
sidebar_pages[key] = Keybinds.structure[key].name;
}
Keybinds.dialog = new Dialog({
id: 'keybindings',
title: 'dialog.settings.keybinds',
singleButton: true,
width: 920,
title_menu: new Menu([
'settings_window',
'keybindings_window',
'theme_window',
'about_window',
]),
sidebar: {
pages: sidebar_pages,
page: 'navigate',
actions: [
'load_keymap',
'export_keymap',
],
onPageSwitch(page) {
Keybinds.dialog.content_vue.open_category = page;
Keybinds.dialog.content_vue.search_term = '';
}
},
component: {
data() {return {
structure: Keybinds.structure,
open_category: 'navigate',
search_term: '',
}},
methods: {
record(item) {
if (!item.keybind) {
item.keybind = new Keybind()
}
item.keybind.record()
},
reset(item) {
if (item.keybind) {
if (item.default_keybind) {
item.keybind.set(item.default_keybind);
} else {
item.keybind.clear();
}
item.keybind.save(true);
}
},
clear(item) {
if (item.keybind) {
item.keybind.clear().save(true)
}
},
toggleCategory(category) {
if (!category.open) {
for (var ct in Keybinds.structure) {
Keybinds.structure[ct].open = false
}
}
category.open = !category.open
}
},
computed: {
list() {
if (this.search_term) {
var keywords = this.search_term.replace(/_/g, ' ').split(' ');
var actions = [];
for (var action of Keybinds.actions) {
if (true) {;
var missmatch = false;
for (var word of keywords) {
if (
!action.name.toLowerCase().includes(word) &&
!action.id.toLowerCase().includes(word) &&
!action.keybind.label.toLowerCase().includes(word)
) {
missmatch = true;
}
}
if (!missmatch) {
actions.push(action)
}
}
}
return actions;
} else {
return this.structure[this.open_category].actions;
}
},
title() {
if (this.search_term) {
return tl('dialog.settings.search_results');
} else {
return this.structure[this.open_category].name;
}
}
},
template: `
<div>
<h2 class="i_b">{{ title }}</h2>
<search-bar id="settings_search_bar" v-model="search_term"></search-bar>
<ul id="keybindlist">
<li v-for="action in list">
<div v-bind:title="action.description">{{action.name}}</div>
<div class="keybindslot" :class="{conflict: action.keybind && action.keybind.conflict}" @click.stop="record(action)" v-html="action.keybind ? action.keybind.getText(true) : ''"></div>
<div class="tool" v-on:click="reset(action)">
<div class="tooltip">${tl('keybindings.reset')}</div>
<i class="material-icons">replay</i>
</div>
<div class="tool" v-on:click="clear(action)">
<div class="tooltip">${tl('keybindings.clear')}</div>
<i class="material-icons">clear</i>
</div>
</li>
</ul>
</div>`
},
onButton() {
Settings.save();
}
})
})
window.addEventListener('blur', event => {
if (Pressing.alt) {

View File

@ -725,9 +725,7 @@ const MenuBar = {
{name: 'menu.help.donate', id: 'donate', icon: 'fas.fa-hand-holding-usd', click: () => {
Blockbench.openLink('https://blockbench.net/donate/');
}},
{name: 'menu.help.about', id: 'about', icon: 'info', click: () => {
Settings.open({tab: 'credits'});
}}
'about_window'
])
MenuBar.update()
},

View File

@ -329,25 +329,6 @@ const Settings = {
items: {}
}
},
open(options = 0) {
for (var sett in settings) {
if (settings.hasOwnProperty(sett)) {
Settings.old[sett] = settings[sett].value
}
}
showDialog('settings')
setSettingsTab(options.tab || 'setting');
if (options.search) {
$('dialog#settings div.search_bar input').val(options.search);
if (options.tab == 'keybindings') {
Keybinds.updateSearch()
} else {
Settings.updateSearch()
}
}
},
saveLocalStorages() {
var settings_copy = {}
for (var key in settings) {
@ -402,42 +383,6 @@ const Settings = {
}
Blockbench.dispatchEvent('update_settings');
},
updateSearch() {
var term = Settings.vue._data.search_term = $('input#settings_search_bar').val().toLowerCase();
var structure = Settings.structure;
if (term) {
var keywords = term.replace(/_/g, ' ').split(' ');
var items = {};
for (var key in settings) {
var setting = settings[key];
if (Condition(setting.condition)) {
var name = tl('settings.'+key).toLowerCase();
var desc = tl('settings.'+key+'.desc').toLowerCase();
var missmatch = false;
for (var word of keywords) {
if (
!key.includes(word) &&
!name.includes(word) &&
!desc.includes(word)
) {
missmatch = true;
}
}
if (!missmatch) {
items[key] = setting;
}
}
}
structure.search_results.items = items
structure.search_results.hidden = false;
for (var key in structure) {
structure[key].open = false
}
structure.search_results.open = true;
} else {
structure.search_results.hidden = true;
}
},
import(file) {
let data = JSON.parse(file.content);
for (let key in settings) {
@ -474,6 +419,18 @@ function updateStreamerModeNotification() {
}
BARS.defineActions(() => {
new Action('settings_window', {
icon: 'settings',
category: 'blockbench',
click: function () {
for (var sett in settings) {
if (settings.hasOwnProperty(sett)) {
Settings.old[sett] = settings[sett].value
}
}
Settings.dialog.show()
}
})
new Action('import_settings', {
icon: 'folder',
@ -530,12 +487,6 @@ BARS.defineActions(() => {
})
onVueSetup(function() {
Settings.structure.search_results = {
name: tl('dialog.settings.search_results'),
hidden: true,
open: true,
items: {}
}
for (var key in settings) {
var category = settings[key].category
if (!category) category = 'general'
@ -549,24 +500,133 @@ onVueSetup(function() {
}
Settings.structure[category].items[key] = settings[key]
}
Settings.vue = new Vue({
el: 'ul#settingslist',
data: {
structure: Settings.structure,
search_term: ''
let sidebar_pages = {};
for (let key in Settings.structure) {
sidebar_pages[key] = Settings.structure[key].name;
}
Settings.dialog = new Dialog({
id: 'settings',
title: 'dialog.settings.settings',
width: 920,
singleButton: true,
title_menu: new Menu([
'settings_window',
'keybindings_window',
'theme_window',
'about_window',
]),
sidebar: {
pages: sidebar_pages,
page: 'general',
actions: [
'import_settings',
'export_settings',
],
onPageSwitch(page) {
Settings.dialog.content_vue.open_category = page;
Settings.dialog.content_vue.search_term = '';
}
},
methods: {
saveSettings() {
Settings.saveLocalStorages();
component: {
data() {return {
structure: Settings.structure,
open_category: 'general',
search_term: '',
}},
methods: {
saveSettings() {
Settings.saveLocalStorages();
}
},
toggleCategory(category) {
if (!category.open) {
for (var ct in Settings.structure) {
Settings.structure[ct].open = false
computed: {
list() {
if (this.search_term) {
var keywords = this.search_term.replace(/_/g, ' ').split(' ');
var items = {};
for (var key in settings) {
var setting = settings[key];
if (Condition(setting.condition)) {
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;
}
}
if (!missmatch) {
items[key] = setting;
}
}
}
return items;
} else {
return this.structure[this.open_category].items;
}
},
title() {
if (this.search_term) {
return tl('dialog.settings.search_results');
} else {
return this.structure[this.open_category].name;
}
}
category.open = !category.open
}
},
template: `
<div>
<h2 class="i_b">{{ title }}</h2>
<search-bar id="settings_search_bar" v-model="search_term"></search-bar>
<ul id="settingslist">
<li v-for="(setting, key) in list" v-if="Condition(setting.condition)" v-on="setting.click ? {click: setting.click} : {}">
<template v-if="setting.type === 'number'">
<div class="setting_element"><input type="number" v-model.number="setting.value" :min="setting.min" :max="setting.max" :step="setting.step" v-on:input="saveSettings()"></div>
</template>
<template v-else-if="setting.type === 'click'">
<div class="setting_element setting_icon" v-html="Blockbench.getIconNode(setting.icon).outerHTML"></div>
</template>
<template v-else-if="setting.type == 'toggle'"><!--TOGGLE-->
<div class="setting_element"><input type="checkbox" v-model="setting.value" v-bind:id="'setting_'+key" v-on:click="saveSettings()"></div>
</template>
<label class="setting_label" v-bind:for="'setting_'+key">
<div class="setting_name">{{ setting.name }}</div>
<div class="setting_description">{{ setting.description }}</div>
</label>
<template v-if="setting.type === 'text'">
<input type="text" class="dark_bordered" style="width: 96%" v-model="setting.value" v-on:input="saveSettings()">
</template>
<template v-if="setting.type === 'password'">
<input :type="setting.hidden ? 'password' : 'text'" class="dark_bordered" style="width: calc(96% - 28px);" v-model="setting.value" v-on:input="saveSettings()">
<div class="password_toggle" @click="setting.hidden = !setting.hidden;">
<i class="fas fa-eye-slash" v-if="setting.hidden"></i>
<i class="fas fa-eye" v-else></i>
</div>
</template>
<template v-else-if="setting.type === 'select'">
<div class="bar_select">
<select v-model="setting.value">
<option v-for="(text, id) in setting.options" v-bind:value="id">{{ text }}</option>
</select>
</div>
</template>
</li>
</ul>
</div>`
},
onButton() {
Settings.save();
}
})
})

View File

@ -4,7 +4,7 @@ const CustomTheme = {
headline_font: '',
code_font: '',
css: '',
colors: {}
colors: {},
},
defaultColors: {
ui: '#282c34',
@ -34,65 +34,153 @@ const CustomTheme = {
localStorage.setItem('theme', JSON.stringify(CustomTheme.data));
}
CustomTheme.vue = new Vue({
el: '#theme_editor',
data: CustomTheme.data,
components: {
VuePrismEditor
CustomTheme.dialog = new Dialog({
id: 'theme',
title: 'dialog.settings.theme',
singleButton: true,
width: 920,
title_menu: new Menu([
'settings_window',
'keybindings_window',
'theme_window',
'about_window',
]),
sidebar: {
pages: {
//discover: tl('layout.discover'),
color: tl('layout.color'),
fonts: tl('layout.fonts'),
css: tl('layout.css'),
},
page: 'color',
actions: [
{
name: 'layout.documentation',
icon: 'fa-book',
click() {
}
},
'import_theme',
'export_theme',
],
onPageSwitch(page) {
CustomTheme.dialog.content_vue.open_category = page;
if (page == 'color' && !CustomTheme.dialog_is_setup) {
CustomTheme.setupDialog()
}
}
},
watch: {
main_font() {
document.body.style.setProperty('--font-custom-main', CustomTheme.data.main_font);
saveChanges();
component: {
data: {
data: CustomTheme.data,
open_category: 'color'
},
headline_font() {
document.body.style.setProperty('--font-custom-headline', CustomTheme.data.headline_font);
saveChanges();
components: {
VuePrismEditor
},
code_font() {
document.body.style.setProperty('--font-custom-code', CustomTheme.data.code_font);
saveChanges();
},
css() {
$('style#theme_css').text(CustomTheme.data.css);
saveChanges();
},
colors: {
handler() {
for (var key in CustomTheme.data.colors) {
var hex = CustomTheme.data.colors[key];
document.body.style.setProperty('--color-'+key, hex);
}
$('meta[name=theme-color]').attr('content', CustomTheme.data.colors.frame);
var c_outline = parseInt('0x'+CustomTheme.data.colors.accent.replace('#', ''))
if (c_outline !== gizmo_colors.outline.getHex()) {
gizmo_colors.outline.set(c_outline)
Canvas.outlineMaterial.color = gizmo_colors.outline
}
var c_wire = parseInt('0x'+CustomTheme.data.colors.wireframe.replace('#', ''))
if (c_wire !== gizmo_colors.wire.getHex()) {
gizmo_colors.wire.set(c_wire);
Canvas.wireframeMaterial.color = gizmo_colors.wire;
}
var c_grid = parseInt('0x'+CustomTheme.data.colors.grid.replace('#', ''))
if (c_grid !== gizmo_colors.grid.getHex()) {
gizmo_colors.grid.set(c_grid);
three_grid.children.forEach(c => {
if (c.name === 'grid' && c.material) {
c.material.color = gizmo_colors.grid;
}
})
}
watch: {
'data.main_font'() {
document.body.style.setProperty('--font-custom-main', CustomTheme.data.main_font);
saveChanges();
},
deep: true
'data.headline_font'() {
document.body.style.setProperty('--font-custom-headline', CustomTheme.data.headline_font);
saveChanges();
},
'data.code_font'() {
document.body.style.setProperty('--font-custom-code', CustomTheme.data.code_font);
saveChanges();
},
'data.css'() {
$('style#theme_css').text(CustomTheme.data.css);
saveChanges();
},
'data.colors': {
handler() {
for (var key in CustomTheme.data.colors) {
var hex = CustomTheme.data.colors[key];
document.body.style.setProperty('--color-'+key, hex);
}
$('meta[name=theme-color]').attr('content', CustomTheme.data.colors.frame);
var c_outline = parseInt('0x'+CustomTheme.data.colors.accent.replace('#', ''))
if (c_outline !== gizmo_colors.outline.getHex()) {
gizmo_colors.outline.set(c_outline)
Canvas.outlineMaterial.color = gizmo_colors.outline
}
var c_wire = parseInt('0x'+CustomTheme.data.colors.wireframe.replace('#', ''))
if (c_wire !== gizmo_colors.wire.getHex()) {
gizmo_colors.wire.set(c_wire);
Canvas.wireframeMaterial.color = gizmo_colors.wire;
}
var c_grid = parseInt('0x'+CustomTheme.data.colors.grid.replace('#', ''))
if (c_grid !== gizmo_colors.grid.getHex()) {
gizmo_colors.grid.set(c_grid);
three_grid.children.forEach(c => {
if (c.name === 'grid' && c.material) {
c.material.color = gizmo_colors.grid;
}
})
}
saveChanges();
},
deep: true
},
},
template: `
<div id="theme_editor">
<div v-if="open_category == 'discover'">
<h2 class="i_b">${tl('layout.discover')}</h2>
</div>
<div v-show="open_category == 'color'">
<h2 class="i_b">${tl('layout.color')}</h2>
<div id="color_wrapper">
<div class="color_field" v-for="(color, key) in data.colors" :id="'color_field_' + key">
<div class="layout_color_preview color_input" :style="{'background-color': color}"></div>
<div class="desc">
<h4>{{ tl('layout.color.'+key) }}</h4>
<p>{{ tl('layout.color.'+key+'.desc') }}</p>
</div>
</div>
</div>
</div>
<div v-if="open_category == 'fonts'">
<h2 class="i_b">${tl('layout.fonts')}</h2>
<div class="dialog_bar">
<label class="name_space_left" for="layout_font_main">${tl('layout.font.main')}</label>
<input style="font-family: var(--font-main)" type="text" class="half dark_bordered" id="layout_font_main" v-model="data.main_font">
</div>
<div class="dialog_bar">
<label class="name_space_left" for="layout_font_headline">${tl('layout.font.headline')}</label>
<input style="font-family: var(--font-headline)" type="text" class="half dark_bordered" id="layout_font_headline" v-model="data.headline_font">
</div>
<div class="dialog_bar">
<label class="name_space_left" for="layout_font_cpde">${tl('layout.font.code')}</label>
<input style="font-family: var(--font-code)" type="text" class="half dark_bordered" id="layout_font_cpde" v-model="data.code_font">
</div>
</div>
<div v-if="open_category == 'css'">
<h2 class="i_b">${tl('layout.css')}</h2>
<div id="css_editor">
<vue-prism-editor v-model="data.css" language="css" :line-numbers="true" />
</div>
</div>
</div>`
},
onButton() {
Settings.save();
}
})
Vue.nextTick(function() {
CustomTheme.fetchFromStorage();
})
@ -214,6 +302,14 @@ const CustomTheme = {
BARS.defineActions(function() {
new Action('theme_window', {
name: tl('dialog.settings.theme') + '...',
icon: 'style',
category: 'blockbench',
click: function () {
CustomTheme.dialog.show();
}
})
new Action('import_theme', {
icon: 'folder',
category: 'blockbench',
@ -271,4 +367,7 @@ BARS.defineActions(function() {
BarItems.import_theme.toElement('#layout_title_bar')
BarItems.export_theme.toElement('#layout_title_bar')
BarItems.reset_theme.toElement('#layout_title_bar')
})
})

View File

@ -501,6 +501,8 @@ BARS.defineActions(function() {
Codecs.project.write(Codecs.project.compile(), Project.save_path);
} else if (Format.codec && Format.codec.export) {
Format.codec.export()
} else {
Project.saved = true;
}
}
if (Format.animation_mode && Format.animation_files && Animation.all.length) {

View File

@ -74,7 +74,7 @@ class Group extends OutlinerNode {
//Clear Old Group
if (Group.selected) Group.selected.unselect()
if (event.shiftKey !== true && event.ctrlOrCmd !== true) {
if ((event.shiftKey || Pressing.overrides.shift) !== true && (event.ctrlOrCmd || Pressing.overrides.ctrl) !== true) {
selected.length = 0
}
//Select This Group

View File

@ -410,7 +410,7 @@ class OutlinerElement extends OutlinerNode {
if (Modes.animate && this.constructor != NullObject) return false;
//Shiftv
var just_selected = []
if (event && event.shiftKey === true && this.getParentArray().includes(selected[selected.length-1]) && !Modes.paint && isOutlinerClick) {
if (event && (event.shiftKey === true || Pressing.overrides.shift) && this.getParentArray().includes(selected[selected.length-1]) && !Modes.paint && isOutlinerClick) {
var starting_point;
var last_selected = selected[selected.length-1]
this.getParentArray().forEach((s, i) => {
@ -441,7 +441,7 @@ class OutlinerElement extends OutlinerNode {
})
//Control
} else if (event && !Modes.paint && (event.ctrlOrCmd || event.shiftKey )) {
} else if (event && !Modes.paint && (event.ctrlOrCmd || event.shiftKey || Pressing.overrides.ctrl || Pressing.overrides.shift)) {
if (selected.includes(this)) {
selected.replace(selected.filter((e) => {
return e !== this
@ -742,7 +742,7 @@ function dropOutlinerObjects(item, target, event, order) {
} else {
var items = [item];
}
if (event.altKey) {
if (event.altKey || Pressing.overrides.alt) {
Undo.initEdit({elements: [], outliner: true, selection: true})
Outliner.selected.empty();
} else {
@ -776,7 +776,7 @@ function dropOutlinerObjects(item, target, event, order) {
}
items.forEach(function(item) {
if (item && item !== target) {
if (event.altKey) {
if (event.altKey || Pressing.overrides.alt) {
if (item instanceof Group) {
var dupl = item.duplicate()
place(dupl)
@ -797,7 +797,7 @@ function dropOutlinerObjects(item, target, event, order) {
if (Format.bone_rig) {
Canvas.updateAllBones()
}
if (event.altKey) {
if (event.altKey || Pressing.overrides.alt) {
updateSelection()
Undo.finishEdit('Duplicate selection', {elements: selected, outliner: true, selection: true})
} else {
@ -1242,7 +1242,7 @@ Interface.definePanels(function() {
convertTouchEvent(e2);
if (e2.target.classList.contains('outliner_toggle') && e2.target.getAttribute('toggle') == key) {
let [node] = eventTargetToNode(e2.target);
if (key == 'visibility' && e2.altKey && !affected.length) {
if (key == 'visibility' && (e2.altKey || Pressing.overrides.alt) && !affected.length) {
let new_affected = Outliner.elements.filter(node => !node.selected);
value = !(new_affected[0] && new_affected[0][key]);
new_affected.forEach(node => {

View File

@ -280,7 +280,8 @@ class Preview {
this.loadBackground()
this.selection = {
box: $('<div id="selection_box" class="selection_rectangle"></div>')
box: $('<div id="selection_box" class="selection_rectangle"></div>'),
frustum: new THREE.Frustum()
}
this.raycaster = new THREE.Raycaster();
@ -682,7 +683,7 @@ class Preview {
}
if (data.element.parent.type === 'group' && (
Animator.open ||
event.shiftKey ||
event.shiftKey || Pressing.overrides.shift ||
(!Format.rotate_cubes && Format.bone_rig && ['rotate_tool', 'pivot_tool'].includes(Toolbox.selected.id))
)) {
data.element.parent.select().showInOutliner();
@ -706,7 +707,7 @@ class Preview {
Toolbox.selected.onCanvasClick({event})
}
if (this.angle !== null && this.camOrtho.axis || this.movingBackground) {
if ((Keybinds.extra.preview_area_select.keybind.isTriggered(event)) || this.movingBackground) {
this.startSelRect(event)
} else {
return false;
@ -806,18 +807,13 @@ class Preview {
this.selection.activated = settings.canvas_unselect.value;
this.selection.old_selected = selected.slice();
var ray = this.raycastMouseCoords(event.clientX, event.clientY)
this.selection.start_u = ray[this.getUVAxes().u]
this.selection.start_v = ray[this.getUVAxes().v]
this.moveSelRect(event)
}
moveSelRect(event) {
var scope = this;
if (this.movingBackground) {
if (event.shiftKey) {
if (event.shiftKey || Pressing.overrides.shift) {
this.background.size = limitNumber( this.background.before.size + (event.offsetY - this.selection.start_y), 0, 10e3)
} else {
this.background.x = this.background.before.x + (event.offsetX - this.selection.start_x);
@ -827,7 +823,6 @@ class Preview {
return;
}
var uv_axes = this.getUVAxes()
//Overlay
var c = getRectangle(
Math.clamp(this.selection.start_x, -2, this.width),
@ -848,45 +843,159 @@ class Preview {
//Select
if (!this.selection.activated) return;
var ray = this.raycastMouseCoords(event.clientX, event.clientY)
/**
* Contains code from https://github.com/mrdoob/three.js/blob/dev/examples/jsm/interactive/SelectionBox.js
* MIT, by three.js contributors
*/
var canvas_offset = $(this.canvas).offset()
let startPoint = new THREE.Vector3(
( (this.selection.client_x - canvas_offset.left) / this.width ) * 2 - 1,
- ( (this.selection.client_y - canvas_offset.top) / this.height ) * 2 + 1,
0.5
);
let endPoint = new THREE.Vector3(
(( event.clientX - canvas_offset.left) / this.width ) * 2 - 1,
- (( event.clientY - canvas_offset.top) / this.height ) * 2 + 1,
0.5
);
if (startPoint.x === endPoint.x) {
endPoint.x += 0.1;
} else if (startPoint.x < endPoint.x) {
[startPoint.x, endPoint.x] = [endPoint.x, startPoint.x];
}
if (startPoint.y === endPoint.y) {
endPoint.y += 0.1;
} else if (startPoint.y > endPoint.y) {
[startPoint.y, endPoint.y] = [endPoint.y, startPoint.y];
}
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();
const _tmpPoint = new THREE.Vector3();
const _vecNear = new THREE.Vector3();
const _vecTopLeft = new THREE.Vector3();
const _vecTopRight = new THREE.Vector3();
const _vecDownRight = new THREE.Vector3();
const _vecDownLeft = new THREE.Vector3();
const _vecFarTopLeft = new THREE.Vector3();
const _vecFarTopRight = new THREE.Vector3();
const _vecFarDownRight = new THREE.Vector3();
const _vecFarDownLeft = new THREE.Vector3();
const _vectemp1 = new THREE.Vector3();
const _vectemp2 = new THREE.Vector3();
const _vectemp3 = new THREE.Vector3();
if ( !this.isOrtho ) {
_tmpPoint.copy( startPoint );
_tmpPoint.x = Math.min( startPoint.x, endPoint.x );
_tmpPoint.y = Math.max( startPoint.y, endPoint.y );
endPoint.x = Math.max( startPoint.x, endPoint.x );
endPoint.y = Math.min( startPoint.y, endPoint.y );
_vecNear.setFromMatrixPosition( this.camera.matrixWorld );
_vecTopLeft.copy( _tmpPoint );
_vecTopRight.set( endPoint.x, _tmpPoint.y, 0 );
_vecDownRight.copy( endPoint );
_vecDownLeft.set( _tmpPoint.x, endPoint.y, 0 );
_vecTopLeft.unproject( this.camera );
_vecTopRight.unproject( this.camera );
_vecDownRight.unproject( this.camera );
_vecDownLeft.unproject( this.camera );
_vectemp1.copy( _vecTopLeft ).sub( _vecNear );
_vectemp2.copy( _vecTopRight ).sub( _vecNear );
_vectemp3.copy( _vecDownRight ).sub( _vecNear );
_vectemp1.normalize();
_vectemp2.normalize();
_vectemp3.normalize();
_vectemp1.multiplyScalar( this.deep );
_vectemp2.multiplyScalar( this.deep );
_vectemp3.multiplyScalar( this.deep );
_vectemp1.add( _vecNear );
_vectemp2.add( _vecNear );
_vectemp3.add( _vecNear );
const planes = this.selection.frustum.planes;
planes[ 0 ].setFromCoplanarPoints( _vecNear, _vecTopLeft, _vecTopRight );
planes[ 1 ].setFromCoplanarPoints( _vecNear, _vecTopRight, _vecDownRight );
planes[ 2 ].setFromCoplanarPoints( _vecDownRight, _vecDownLeft, _vecNear );
planes[ 3 ].setFromCoplanarPoints( _vecDownLeft, _vecTopLeft, _vecNear );
planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft );
planes[ 5 ].setFromCoplanarPoints( _vectemp3, _vectemp2, _vectemp1 );
planes[ 5 ].normal.multiplyScalar( - 1 );
} else {
const left = Math.min( startPoint.x, endPoint.x );
const top = Math.max( startPoint.y, endPoint.y );
const right = Math.max( startPoint.x, endPoint.x );
const down = Math.min( startPoint.y, endPoint.y );
_vecTopLeft.set( left, top, - 1 );
_vecTopRight.set( right, top, - 1 );
_vecDownRight.set( right, down, - 1 );
_vecDownLeft.set( left, down, - 1 );
_vecFarTopLeft.set( left, top, 1 );
_vecFarTopRight.set( right, top, 1 );
_vecFarDownRight.set( right, down, 1 );
_vecFarDownLeft.set( left, down, 1 );
_vecTopLeft.unproject( this.camera );
_vecTopRight.unproject( this.camera );
_vecDownRight.unproject( this.camera );
_vecDownLeft.unproject( this.camera );
_vecFarTopLeft.unproject( this.camera );
_vecFarTopRight.unproject( this.camera );
_vecFarDownRight.unproject( this.camera );
_vecFarDownLeft.unproject( this.camera );
const planes = this.selection.frustum.planes;
planes[ 0 ].setFromCoplanarPoints( _vecTopLeft, _vecFarTopLeft, _vecFarTopRight );
planes[ 1 ].setFromCoplanarPoints( _vecTopRight, _vecFarTopRight, _vecFarDownRight );
planes[ 2 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarDownLeft, _vecDownLeft );
planes[ 3 ].setFromCoplanarPoints( _vecFarDownLeft, _vecFarTopLeft, _vecTopLeft );
planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft );
planes[ 5 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarTopRight, _vecFarTopLeft );
planes[ 5 ].normal.multiplyScalar( - 1 );
}
let box = new THREE.Box3();
let vector = new THREE.Vector3();
var plane_rect = getRectangle(
this.selection.start_u,
this.selection.start_v,
ray[uv_axes.u],
ray[uv_axes.v]
)
unselectAll()
Outliner.elements.forEach(function(cube) {
Outliner.elements.forEach((element) => {
let isSelected;
if ((event.shiftKey || event.ctrlOrCmd || Pressing.overrides.ctrl || Pressing.overrides.shift) && scope.selection.old_selected.indexOf(element) >= 0) {
isSelected = true
if ((event.shiftKey || event.ctrlOrCmd) && scope.selection.old_selected.indexOf(cube) >= 0) {
var isSelected = true
} else {
if (cube instanceof Cube && cube.visibility && cube.mesh) {
var mesh = cube.mesh
var from = new THREE.Vector3().copy(mesh.geometry.vertices[6]).applyMatrix4(mesh.matrixWorld)
var to = new THREE.Vector3().copy(mesh.geometry.vertices[0]).applyMatrix4(mesh.matrixWorld)
var cube_rect = getRectangle(
from[uv_axes.u],
from[uv_axes.v],
to[uv_axes.u],
to[uv_axes.v]
)
var isSelected = doRectanglesOverlap(plane_rect, cube_rect)
} else if (cube instanceof Locator && cube.parent instanceof Group && cube.parent.mesh) {
var mesh = cube.parent.mesh;
var pos = new THREE.Vector3().fromArray(cube.from).applyMatrix4(mesh.matrixWorld);
var cube_rect = getRectangle(
pos[uv_axes.u],
pos[uv_axes.v],
pos[uv_axes.u],
pos[uv_axes.v]
)
var isSelected = doRectanglesOverlap(plane_rect, cube_rect)
} else if (element.visibility) {
if (element.mesh && element.mesh.geometry) {
box.copy(element.mesh.geometry.boundingBox).applyMatrix4(element.mesh.matrixWorld);
isSelected = this.selection.frustum.intersectsBox(box);
} else if (element.mesh) {
element.mesh.getWorldPosition(vector);
isSelected = this.selection.frustum.containsPoint(vector);
}
}
if (isSelected) {
cube.selectLow()
element.selectLow()
}
})
TickUpdates.selection = true;
@ -901,13 +1010,6 @@ class Preview {
this.selection.box.detach()
this.selection.activated = false;
}
getUVAxes() {
switch (this.camOrtho.axis) {
case 'x': return {u: 'z', v: 'y'}; break;
case 'y': return {u: 'x', v: 'z'}; break;
case 'z': return {u: 'x', v: 'y'}; break;
}
}
//Backgrounds
getBackground() {

View File

@ -26,7 +26,10 @@
if ( highlighted ) {
this.color.setHex( parseInt(CustomTheme.data.colors.accent.replace('#', ''), 16) );
this.color.copy( gizmo_colors.outline );
this.color.r *= 1.2;
this.color.g *= 1.2;
this.color.b *= 1.2;
this.opacity = 1;
} else {
@ -58,7 +61,10 @@
if ( highlighted ) {
this.color.setHex( parseInt(CustomTheme.data.colors.accent.replace('#', ''), 16) );
this.color.copy( gizmo_colors.outline );
this.color.r *= 1.2;
this.color.g *= 1.2;
this.color.b *= 1.2;
this.opacity = 1;
} else {
@ -179,8 +185,7 @@
if ( child.material && child.material.highlight ) {
if ( child.name === axis && axis_letter && child.scale[axis_letter] < 5) {
if ( child.name === axis && axis_letter && (child.scale[axis_letter] < 5 || axis == 'E') ) {
child.material.highlight( true );
@ -415,7 +420,11 @@
[ new THREE.Line( new CircleGeometry( 1, 'z', 0.5 ), new GizmoLineMaterial( { color: gizmo_colors.b } ) ) ],
[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.06, 0 ), new GizmoLineMaterial( { color: gizmo_colors.b } ) ), [ 0.98, 0, 0 ], null, [ 1, 4, 1 ] ],
],
XYZE: [[ new THREE.Line( new CircleGeometry( 1, 'z', 1 ), new GizmoLineMaterial( { color: 0x787878 } ) ) ]]
E: [
[ new THREE.Line( new CircleGeometry( 1.2, 'z', 1 ), new GizmoLineMaterial( { color: gizmo_colors.outline } ) ) ]
],
XYZE: [[ new THREE.Line( new CircleGeometry( 1, 'z', 1 ), new GizmoLineMaterial( { color: gizmo_colors.grid } ) ) ]]
};
@ -423,7 +432,8 @@
X: [[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ] ]],
Y: [[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ]],
Z: [[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ]]
Z: [[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ]],
E: [[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.2, 0.12, 2, 24 ), pickerMaterial ) ]],
};
@ -443,13 +453,6 @@
THREE.TransformGizmo.prototype.update.apply( this, arguments );
var group = {
handles: this[ "handles" ],
pickers: this[ "pickers" ],
};
var tempMatrix = new THREE.Matrix4();
var worldRotation = new THREE.Euler( 0, 0, 1 );
var tempQuaternion = new THREE.Quaternion();
@ -1130,6 +1133,7 @@
var axis = scope.axis.substr(-1).toLowerCase()
var axisNumber = getAxisNumber(axis)
var rotate_normal;
point.copy( planeIntersect.point );
@ -1140,14 +1144,24 @@
}
} else {
point.sub( worldPosition );
point.removeEuler(worldRotation)
var rotations = [
Math.atan2( point.z, point.y ),
Math.atan2( point.x, point.z ),
Math.atan2( point.y, point.x )
]
var angle = Math.radToDeg( rotations[axisNumber] )
point.removeEuler(worldRotation);
if (scope.axis == 'E') {
let matrix = new THREE.Matrix4().copy(_gizmo[ _mode ].activePlane.matrix).invert();
point.applyMatrix4(matrix)
var angle = Math.radToDeg( Math.atan2( point.y, point.x ) )
rotate_normal = Preview.selected.camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(-1);
} else {
var rotations = [
Math.atan2( point.z, point.y ),
Math.atan2( point.x, point.z ),
Math.atan2( point.y, point.x )
]
var angle = Math.radToDeg( rotations[axisNumber] )
}
}
let transform_space = Transformer.getTransformSpace()
@ -1226,6 +1240,9 @@
beforeFirstChange(event)
var difference = angle - previousValue
if (axisNumber == undefined) {
axisNumber = rotate_normal;
}
rotateOnAxis(n => (n + difference), axisNumber)
Canvas.updatePositions(true)
scope.updateSelection()
@ -1354,10 +1371,12 @@
} else {
let {mesh} = Group.selected;
if (Toolbox.selected.id === 'rotate_tool' && BarItems.rotation_space.value === 'global') {
if (axisNumber != 2) difference *= -1;
if (Toolbox.selected.id === 'rotate_tool' && (BarItems.rotation_space.value === 'global' || scope.axis == 'E')) {
let normal = axisNumber == 0 ? THREE.NormalX : (axisNumber == 1 ? THREE.NormalY : THREE.NormalZ)
let normal = scope.axis == 'E'
? rotate_normal
: axisNumber == 0 ? THREE.NormalX : (axisNumber == 1 ? THREE.NormalY : THREE.NormalZ);
if (axisNumber != 2) difference *= -1;
let rotWorldMatrix = new THREE.Matrix4();
rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(difference))
rotWorldMatrix.multiply(mesh.matrixWorld)
@ -1426,7 +1445,7 @@
var channel = Toolbox.selected.animation_channel
if (channel === 'position') channel = 'translation';
var value = point[axis]
var bf = Project.display_settings[display_slot][channel][axisNumber] - (previousValue||0)
var bf = (Project.display_settings[display_slot][channel][axisNumber] - (previousValue||0)) || 0;
if (channel === 'rotation') {
value = Math.trimDeg(bf + Math.round(angle*4)/4) - bf;
@ -1451,10 +1470,26 @@
if (value !== previousValue) {
beforeFirstChange(event)
var difference = value - (previousValue||0)
Project.display_settings[display_slot][channel][axisNumber] += difference
var difference = value - (previousValue||0);
if (event.shiftKey && channel === 'scale') {
if (channel === 'rotation' && scope.axis == 'E') {
let normal = scope.axis == 'E'
? rotate_normal
: axisNumber == 0 ? THREE.NormalX : (axisNumber == 1 ? THREE.NormalY : THREE.NormalZ);
let quaternion = display_base.getWorldQuaternion(new THREE.Quaternion()).invert()
normal.applyQuaternion(quaternion)
display_base.rotateOnAxis(normal, Math.degToRad(difference))
Project.display_settings[display_slot][channel][0] = Math.roundTo(Math.radToDeg(display_base.rotation.x), 2);
Project.display_settings[display_slot][channel][1] = Math.roundTo(Math.radToDeg(display_base.rotation.y) * (display_slot.includes('lefthand') ? -1 : 1), 2);
Project.display_settings[display_slot][channel][2] = Math.roundTo(Math.radToDeg(display_base.rotation.z) * (display_slot.includes('lefthand') ? -1 : 1), 2);
} else {
Project.display_settings[display_slot][channel][axisNumber] += difference;
}
if ((event.shiftKey || Pressing.overrides.shift) && channel === 'scale') {
var val = Project.display_settings[display_slot][channel][axisNumber]
Project.display_settings[display_slot][channel][(axisNumber+1)%3] = val
Project.display_settings[display_slot][channel][(axisNumber+2)%3] = val

View File

@ -728,8 +728,8 @@ BARS.defineActions(function() {
min: 0, max: 360, default: 0,
},
getInterval(e) {
if (e.shiftKey) return 12.5;
if (e.ctrlOrCmd) return 1;
if (e.shiftKey || Pressing.overrides.shift) return 12.5;
if (e.ctrlOrCmd || Pressing.overrides.ctrl) return 1;
return 4
},
get: function() {
@ -748,8 +748,8 @@ BARS.defineActions(function() {
min: 0, max: 100, default: 0,
},
getInterval(e) {
if (e.shiftKey) return 10;
if (e.ctrlOrCmd) return 1;
if (e.shiftKey || Pressing.overrides.shift) return 10;
if (e.ctrlOrCmd || Pressing.overrides.ctrl) return 1;
return 2
},
get: function() {
@ -768,8 +768,8 @@ BARS.defineActions(function() {
min: 0, max: 100, default: 100,
},
getInterval(e) {
if (e.shiftKey) return 10;
if (e.ctrlOrCmd) return 1;
if (e.shiftKey || Pressing.overrides.shift) return 10;
if (e.ctrlOrCmd || Pressing.overrides.ctrl) return 1;
return 2
},
get: function() {

View File

@ -187,12 +187,12 @@ const Painter = {
Painter.painting = true;
if (data) {
var is_line = event.shiftKey && Painter.current.cube == data.element && Painter.current.face == data.face
var is_line = (event.shiftKey || Pressing.overrides.shift) && Painter.current.cube == data.cube && Painter.current.face == data.face
Painter.current.cube = data.element;
Painter.current.face = data.face;
} else {
//uv editor
var is_line = event.shiftKey;
var is_line = (event.shiftKey || Pressing.overrides.shift);
}
if (is_line) {
@ -528,7 +528,7 @@ const Painter = {
let diff_x = x - Painter.startPixel[0];
let diff_y = y - Painter.startPixel[1];
if (event.shiftKey) {
if (event.shiftKey || Pressing.overrides.shift) {
let clamp = Math.floor((Math.abs(diff_x) + Math.abs(diff_y))/2);
diff_x = diff_x>0 ? clamp : -clamp;
diff_y = diff_y>0 ? clamp : -clamp;
@ -643,7 +643,7 @@ const Painter = {
let diff_x = x - Painter.startPixel[0];
let diff_y = y - Painter.startPixel[1];
if (event.shiftKey) {
if (event.shiftKey || Pressing.overrides.shift) {
let length = Math.sqrt(Math.pow(diff_x, 2) + Math.pow(diff_y, 2));
let ratio = Math.abs(diff_x) / Math.abs(diff_y);

View File

@ -1223,7 +1223,7 @@ function loadTextureDraggable() {
Undo.initEdit({elements: cubes_list})
cubes_list.forEach(cube => {
if (cube instanceof Cube) {
cube.applyTexture(tex, data.shiftKey || [data.face])
cube.applyTexture(tex, data.shiftKey || Pressing.overrides.shift || [data.face])
}
})
Undo.finishEdit('Apply texture')

View File

@ -258,7 +258,7 @@ class UVEditor {
var offset = scope.jquery.frame.offset();
event.offsetX = event.clientX - offset.left;
event.offsetY = event.clientY - offset.top;
if (!dragging_not_clicking && event.ctrlOrCmd) {
if (!dragging_not_clicking && (event.ctrlOrCmd || Pressing.overrides.ctrl)) {
scope.reverseSelect(event)
}
dragging_not_clicking = false;
@ -1787,7 +1787,7 @@ const uv_dialog = {
BARS.updateConditions()
},
select: function(id, event) {
if (event.shiftKey) {
if (event.shiftKey || Pressing.overrides.shift) {
uv_dialog.selection.push(id)
} else {
if (uv_dialog.selection.includes(id) && uv_dialog.selection.length === 1) {

View File

@ -755,7 +755,7 @@ function rotateOnAxis(modify, axis, slider) {
}
}
var axis_letter = getAxisLetter(axis)
var origin =things[0].origin
var origin = things[0].origin
things.forEach(function(obj, i) {
if (!obj.rotation.allEqual(0)) {
origin = obj.origin
@ -763,6 +763,7 @@ function rotateOnAxis(modify, axis, slider) {
})
let space = Transformer.getTransformSpace()
if (axis instanceof THREE.Vector3) space = 0;
things.forEach(obj => {
let mesh = obj.mesh;
if (obj instanceof Cube && !Format.bone_rig) {
@ -821,7 +822,9 @@ function rotateOnAxis(modify, axis, slider) {
obj.rotation[2] = Math.radToDeg(e.z);
} else if (space == 0) {
let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ)
let normal = axis instanceof THREE.Vector3
? axis
: axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ)
let rotWorldMatrix = new THREE.Matrix4();
rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0)))
rotWorldMatrix.multiply(mesh.matrixWorld)

View File

@ -1,14 +1,18 @@
//Blockbench
function compareVersions(string1/*new*/, string2/*old*/) {
// Is string1 newer than string2 ?
var arr1 = string1.split('.')
var arr2 = string2.split('.')
var arr1 = string1.split(/[.-]/);
var arr2 = string2.split(/[.-]/);
var i = 0;
var num1 = 0;
var num2 = 0;
while (i < arr1.length) {
num1 = parseInt(arr1[i])
num2 = parseInt(arr2[i])
while (i < Math.max(arr1.length, arr2.length)) {
num1 = arr1[i];
num2 = arr2[i];
if (num1 == 'beta') num1 = -1;
if (num2 == 'beta') num2 = -1;
num1 = parseInt(num1) || 0;
num2 = parseInt(num2) || 0;
if (num1 > num2) {
return true;
} else if (num1 < num2) {
@ -563,6 +567,13 @@ var Merge = {
}
}
function onVueSetup(func) {
if (!onVueSetup.funcs) {
onVueSetup.funcs = []
}
onVueSetup.funcs.push(func)
}
//String
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@
"preview_rotate": {"key": 2},
"preview_drag": {"key": 2, "shift": true},
"preview_zoom": {"key": 2, "ctrl": true},
"preview_area_select": {"key": 1},
"confirm": {"key": 13},
"cancel": {"key": 27},
"move_tool": {"key": 71},

View File

@ -4,6 +4,7 @@
"preview_rotate": {"key": 1, "alt": true},
"preview_drag": {"key": 2, "alt": true},
"preview_zoom": {"key": 1, "shift": true},
"preview_area_select": {"key": 1},
"confirm": {"key": 13},
"cancel": {"key": 27},
"move_tool": {"key": 69},

View File

@ -4,6 +4,7 @@
"preview_rotate": {"key": 1, "alt": true},
"preview_drag": {"key": 2, "alt": true},
"preview_zoom": {"key": 3, "alt": true},
"preview_area_select": {"key": 1},
"confirm": {"key": 13},
"cancel": {"key": 27},
"move_tool": {"key": 87},

View File

@ -24,6 +24,8 @@
"data.preview": "Preview",
"data.toolbar": "Toolbar",
"data.separator": "Separator",
"data.separator.spacer": "Spacer",
"data.separator.linebreak": "Line Break",
"data.image": "Image",
"data.format": "Format",
"data.effect": "Effect",
@ -341,6 +343,7 @@
"dialog.toolbar_edit.title": "Customize Toolbar",
"dialog.toolbar_edit.hidden": "Hidden",
"dialog.toolbar_edit.hidden_tools": "Some tools might be hidden because they are not available in the current mode, format, or situation.",
"dialog.toolbar_edit.incompatible_separators": "Spacers and Line Breaks are mutually exclusive, you cannot use both in the same toolbar.",
"dialog.entitylist.title": "Open Entity Model",
"dialog.entitylist.text": "Select the model you want to import",
@ -431,7 +434,6 @@
"dialog.export_private_settings.keep": "Keep",
"dialog.export_private_settings.omit": "Leave Out",
"dialog.settings.title": "Preferences",
"dialog.settings.settings": "Settings",
"dialog.settings.keybinds": "Keybindings",
"dialog.settings.theme": "Theme",
@ -443,6 +445,11 @@
"keybindings.recording": "Recording Keybinding",
"keybindings.press": "Press a key or key combination or click anywhere on the screen to record your keybinding.",
"layout.discover": "Discover",
"layout.color": "Color Scheme",
"layout.fonts": "Fonts",
"layout.css": "Custom CSS",
"layout.documentation": "Documentation",
"layout.color.back": "Back",
"layout.color.back.desc": "Backgrounds and input fields",
"layout.color.dark": "Dark",
@ -478,7 +485,6 @@
"layout.font.main": "Main Font",
"layout.font.headline": "Headline Font",
"layout.font.code": "Code Font",
"layout.css": "Custom CSS",
"about.version": "Version:",
"about.version.up_to_date": "Up to date",
@ -486,7 +492,6 @@
"about.creator": "Creator:",
"about.website": "Website:",
"about.repository": "Repository:",
"about.electron": "This app is built with Electron, a framework for creating native applications with web technologies like Javascript, HTML, and CSS.",
"about.vertex_snap": "Vertex Snapping is based on a plugin by SirBenet",
"about.icons": "Icon Packs:",
"about.libraries": "Libraries:",
@ -666,6 +671,7 @@
"keybind.preview_rotate": "Rotate View",
"keybind.preview_drag": "Drag View",
"keybind.preview_zoom": "Zoom View",
"keybind.preview_area_select": "Area Select",
"keybind.confirm": "Confirm",
"keybind.cancel": "Cancel",

View File

@ -1,7 +1,7 @@
{
"name": "Blockbench",
"description": "Model editing and animation software",
"version": "3.9.2",
"version": "4.0.0-beta.0",
"license": "GPL-3.0-or-later",
"author": {
"name": "JannisX11",