blockbench/js/interface/settings.js
JannisX11 502c2b3321 Fix settings and keybindings search bar character casing issue
Fix keybinding conflicts not showing on start
Fix some settings not updating correctly
Fix copy paste tool accuracy issue
2022-06-10 17:55:13 +02:00

648 lines
22 KiB
JavaScript

const settings = {};
class Setting {
constructor(id, data) {
this.id = id;
settings[id] = this;
this.type = 'toggle';
if (data.type) this.type = data.type;
if (Settings.stored[id]) {
this.value = Settings.stored[id].value;
} else if (data.value != undefined) {
this.value = data.value
} else {
switch (this.type) {
case 'toggle': this.value = true; break;
case 'number': this.value = 0; break;
case 'text': this.value = ''; break;
case 'password': this.value = ''; break;
case 'select': this.value; break;
case 'click': this.value = false; break;
}
}
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.launch_setting = data.launch_setting || false;
if (this.type == 'number') {
this.min = data.min;
this.max = data.max;
this.step = data.step;
}
if (this.type == 'click') {
this.icon = data.icon;
this.click = data.click;
}
if (this.type == 'select') {
this.options = data.options;
}
if (this.type == 'password') {
this.hidden = true;
}
if (typeof data.onChange == 'function') {
this.onChange = data.onChange
}
//add to structure
var category = Settings.structure[this.category];
if (category) {
category.items[id] = this;
let before = category.open;
category.open = false;
Vue.nextTick(() => {
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');
}
delete() {
if (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];
}
}
set(value) {
if (value === undefined || value === null) return;
let old_value = this.value;
if (this.type == 'number' && typeof value == 'number') {
if (this.snap) {
value = Math.round(value / this.snap) * this.snap;
}
this.value = Math.clamp(value, this.min, this.max)
} else if (this.type == 'toggle') {
this.value = !!value;
} else if (this.type == 'click') {
this.value = value;
} else if (typeof value == 'string') {
this.value = value;
}
if (typeof this.onChange == 'function' && this.value !== old_value) {
this.onChange(this.value);
}
Settings.saveLocalStorages();
}
trigger(e) {
let {type} = this;
let setting = this;
if (type == 'toggle') {
this.set(!this.value);
Settings.save();
} else if (type == 'click') {
this.click(e)
} else if (type == 'select') {
let list = [];
for (let key in this.options) {
list.push({
id: key,
name: this.options[key],
icon: this.value == key
? 'radio_button_checked'
: 'radio_button_unchecked',
click: () => {
this.set(key);
Settings.save();
}
})
}
new Menu(list).open(e.target);
} else if (type == 'click') {
this.click(e)
} else {
new Dialog({
id: 'setting_' + this.id,
title: tl('data.setting'),
form: {
input: {
value: this.value,
label: this.name,
description: this.description,
type: this.type
},
description: this.description ? {
type: 'info',
text: this.description
} : undefined
},
onConfirm({input}) {
setting.set(input);
Settings.save();
this.hide().delete();
},
onCancel() {
this.hide().delete();
}
}).show();
}
}
}
const Settings = {
structure: {},
stored: {},
setup() {
if (localStorage.getItem('settings') != null) {
Settings.stored = JSON.parse(localStorage.getItem('settings'));
}
//General
new Setting('language', {value: 'en', type: 'select', options: Language.options});
new Setting('username', {value: '', type: 'text'});
new Setting('streamer_mode', {value: false, onChange() {
StartScreen.vue._data.redact_names = settings.streamer_mode.value;
Interface.status_bar.vue.streamer_mode = settings.streamer_mode.value;
updateStreamerModeNotification();
}});
//Interface
new Setting('interface_scale', {category: 'interface', value: 100, min: 40, max: 200, type: 'number', condition: isApp, onChange() {
var factor = Math.clamp(settings.interface_scale.value, 40, 200) / 100;
currentwindow.webContents.setZoomFactor(factor)
resizeWindow()
}});
new Setting('hide_tab_bar', {category: 'interface', value: Blockbench.isMobile, onChange() {
updateTabBarVisibility();
}});
new Setting('mobile_panel_side', {category: 'interface', value: 'right', condition: Blockbench.isMobile, type: 'select', options: {
'right': tl('generic.right'),
'left': tl('generic.left'),
}, onChange() {
document.body.classList.toggle('mobile_sidebar_left', settings.mobile_panel_side.value == 'left');
}});
new Setting('status_bar_modifier_keys', {category: 'interface', value: true, condition: !Blockbench.isTouch, onChange(value) {
Interface.status_bar.vue.show_modifier_keys = value;
}});
new Setting('origin_size', {category: 'interface', value: 10, type: 'number'});
new Setting('control_size', {category: 'interface', value: 10, type: 'number'});
new Setting('motion_trails', {category: 'interface', value: true, onChange() {
if (Animator.open) {
scene[this.value ? 'add' : 'remove'](Animator.motion_trail);
}
}});
new Setting('seethrough_outline', {category: 'interface', value: false});
new Setting('outliner_colors', {category: 'interface', value: false});
new Setting('preview_checkerboard', {category: 'interface', value: true, onChange() {
$('#center').toggleClass('checkerboard', settings.preview_checkerboard.value);
}});
new Setting('uv_checkerboard', {category: 'interface', value: true, onChange(val) {
UVEditor.vue.checkerboard = val;
}});
new Setting('timecode_frame_number',{category: 'interface', value: false, onChange() {
Timeline.vue.updateTimecodes();
}});
//Preview
new Setting('brightness', {category: 'preview', value: 50, type: 'number'});
new Setting('shading', {category: 'preview', value: true, onChange() {
updateShading()
}});
new Setting('antialiasing', {category: 'preview', value: true});
new Setting('fov', {category: 'preview', value: 45, type: 'number', onChange(val) {
Preview.all.forEach(preview => preview.setFOV(val));
}});
new Setting('camera_near_plane',{category: 'preview', value: 1, type: 'number', onChange(val) {
Preview.all.forEach(preview => {
preview.camPers.near = val;
preview.camPers.updateProjectionMatrix();
});
}});
new Setting('render_sides', {category: 'preview', value: 'auto', type: 'select', options: {
'auto': tl('settings.render_sides.auto'),
'front': tl('settings.render_sides.front'),
'double': tl('settings.render_sides.double'),
}, onChange() {
Canvas.updateRenderSides();
}});
new Setting('background_rendering', {category: 'preview', value: true});
new Setting('texture_fps', {category: 'preview', value: 7, type: 'number', onChange() {
TextureAnimator.updateSpeed()
}});
new Setting('particle_tick_rate',{category: 'preview', value: 30, type: 'number', onChange() {
WinterskyScene.global_options.tick_rate = this.value;
}});
new Setting('volume', {category: 'preview', value: 80, type: 'number'});
new Setting('display_skin', {category: 'preview', value: false, type: 'click', condition: isApp, icon: 'icon-player', click: function() { changeDisplaySkin() }});
//Edit
new Setting('undo_limit', {category: 'edit', value: 256, type: 'number'});
new Setting('canvas_unselect', {category: 'edit', value: false});
new Setting('highlight_cubes', {category: 'edit', value: true, onChange() {
updateCubeHighlights();
}});
new Setting('deactivate_size_limit',{category: 'edit', value: false});
new Setting('vertex_merge_distance',{category: 'edit', value: 0.1, step: 0.01, type: 'number'});
new Setting('preview_paste_behavior',{category: 'edit', value: 'always_ask', type: 'select', options: {
'always_ask': tl('settings.preview_paste_behavior.always_ask'),
'outliner': tl('menu.paste.outliner'),
'face': tl('menu.paste.face'),
'mesh_selection': tl('menu.paste.mesh_selection'),
}});
//Grid
new Setting('base_grid', {category: 'grid', value: true,});
new Setting('large_grid', {category: 'grid', value: true});
new Setting('full_grid', {category: 'grid', value: false});
new Setting('large_box', {category: 'grid', value: false});
new Setting('large_grid_size', {category: 'grid', value: 3, type: 'number'});
new Setting('display_grid', {category: 'grid', value: false});
new Setting('painting_grid', {category: 'grid', value: true, onChange() {
Canvas.updatePaintingGrid();
}});
new Setting('ground_plane', {category: 'grid', value: false, onChange() {
Canvas.ground_plane.visible = this.value;
}});
//Snapping
new Setting('edit_size', {category: 'snapping', value: 16, type: 'number'});
new Setting('shift_size', {category: 'snapping', value: 64, type: 'number'});
new Setting('ctrl_size', {category: 'snapping', value: 160, type: 'number'});
new Setting('ctrl_shift_size', {category: 'snapping', value: 640, type: 'number'});
new Setting('negative_size',{category: 'snapping', value: false});
//Paint
new Setting('sync_color', {category: 'paint', value: false});
new Setting('color_wheel', {category: 'paint', value: false, onChange(value) {
Interface.Panels.color.vue.picker_type = value ? 'wheel' : 'box';
}});
new Setting('paint_side_restrict', {category: 'paint', value: true});
new Setting('brush_opacity_modifier', {category: 'paint', value: 'pressure', type: 'select', options: {
'pressure': tl('settings.brush_modifier.pressure'),
'tilt': tl('settings.brush_modifier.tilt'),
'none': tl('settings.brush_modifier.none'),
}});
new Setting('brush_size_modifier', {category: 'paint', value: 'tilt', type: 'select', options: {
'pressure': tl('settings.brush_modifier.pressure'),
'tilt': tl('settings.brush_modifier.tilt'),
'none': tl('settings.brush_modifier.none'),
}});
new Setting('image_editor', {category: 'paint', value: false, type: 'click', condition: isApp, icon: 'fas.fa-pen-square', click: function() {changeImageEditor(null, true) }});
//Defaults
new Setting('autouv', {category: 'defaults', value: true});
new Setting('create_rename', {category: 'defaults', value: false});
new Setting('show_only_selected_uv', {category: 'defaults', value: false});
new Setting('default_path', {category: 'defaults', value: false, type: 'click', condition: isApp, icon: 'burst_mode', click: function() { openDefaultTexturePath() }});
new Setting('animation_snap', {category: 'defaults', value: 24, type: 'number'});
//Dialogs
new Setting('dialog_larger_cubes', {category: 'dialogs', value: true, name: tl('message.model_clipping.title'), description: tl('settings.dialog.desc', [tl('message.model_clipping.title')])});
new Setting('dialog_rotation_limit', {category: 'dialogs', value: true, name: tl('message.rotation_limit.title'), description: tl('settings.dialog.desc', [tl('message.rotation_limit.title')])});
new Setting('dialog_loose_texture', {category: 'dialogs', value: true, name: tl('message.loose_texture.title'), description: tl('settings.dialog.desc', [tl('message.loose_texture.title')])});
//Application
new Setting('recent_projects', {category: 'application', value: 32, max: 256, min: 0, type: 'number', condition: isApp});
new Setting('backup_interval', {category: 'application', value: 10, type: 'number', condition: isApp});
new Setting('backup_retain', {category: 'application', value: 30, type: 'number', condition: isApp});
new Setting('automatic_updates', {category: 'application', value: true, condition: isApp});
new Setting('update_to_prereleases', {category: 'application', value: false, condition: isApp, launch_setting: true});
new Setting('hardware_acceleration', {category: 'application', value: true, condition: isApp, launch_setting: true});
//Export
new Setting('minifiedout', {category: 'export', value: false});
new Setting('minify_bbmodel', {category: 'export', value: true});
new Setting('export_empty_groups', {category: 'export', value: true});
new Setting('export_groups', {category: 'export', value: true});
new Setting('obj_face_export_mode', {category: 'export', value: 'both', type: 'select', options: {
both: tl('settings.obj_face_export_mode.both'),
tris: tl('settings.obj_face_export_mode.tris'),
quads: tl('settings.obj_face_export_mode.quads'),
}});
new Setting('animation_sample_rate',{category: 'export', value: 24, type: 'number'});
new Setting('sketchfab_token', {category: 'export', value: '', type: 'password'});
new Setting('credit', {category: 'export', value: 'Made with Blockbench', type: 'text'});
Blockbench.onUpdateTo('3.8', () => {
settings.preview_checkerboard.value = true;
settings.uv_checkerboard.value = true;
})
},
addCategory(id, data = {}) {
Settings.structure[id] = {
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();
},
saveLocalStorages() {
var settings_copy = {}
for (var key in settings) {
settings_copy[key] = {value: settings[key].value}
}
localStorage.setItem('settings', JSON.stringify(settings_copy) )
if (window.canvas_scenes) {
localStorage.setItem('canvas_scenes', JSON.stringify(canvas_scenes))
}
if (window.ColorPanel) {
ColorPanel.saveLocalStorages()
}
},
save() {
Settings.saveLocalStorages()
function hasSettingChanged(id) {
return (settings[id].value !== Settings.old[id])
}
updateSelection()
for (var key in BarItems) {
var action = BarItems[key]
if (action.linked_setting) {
if (settings[action.linked_setting] && action.value != settings[action.linked_setting].value) {
action.value = settings[action.linked_setting].value;
action.updateEnabledState();
}
}
}
if (hasSettingChanged('base_grid') || hasSettingChanged('large_grid') || hasSettingChanged('full_grid') || hasSettingChanged('large_grid_size')
||hasSettingChanged('large_box') || hasSettingChanged('display_grid') || hasSettingChanged('edit_size')) {
Canvas.buildGrid()
}
Canvas.outlineMaterial.depthTest = !settings.seethrough_outline.value
if (hasSettingChanged('brightness')) {
updateShading()
}
for (var id in settings) {
var setting = settings[id];
if (!Condition(setting.condition)) continue;
if (setting.onChange && hasSettingChanged(id)) {
setting.onChange(setting.value);
}
if (isApp && setting.launch_setting && hasSettingChanged(id)) {
ipcRenderer.send('edit-launch-setting', {key: id, value: setting.value})
}
}
Blockbench.dispatchEvent('update_settings');
},
import(file) {
let data = JSON.parse(file.content);
for (let key in settings) {
let setting = settings[key];
if (setting instanceof Setting && data.settings[key] !== undefined) {
setting.set(data.settings[key]);
}
}
},
get(id) {
if (id && settings[id]) {
return settings[id].value;
}
},
old: {}
}
Settings.setup()
function updateStreamerModeNotification() {
$('#start_screen section#streamer_mode').detach()
if (settings.streamer_mode.value) {
addStartScreenSection('streamer_mode', {
graphic: {type: 'icon', icon: 'live_tv'},
color: 'var(--color-stream)',
text_color: 'var(--color-light)',
text: [
{type: 'h1', text: tl('interface.streamer_mode_on'), click() {
ActionControl.select(`setting: ${tl('settings.streamer_mode')}`);
}}
]
})
}
}
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()
document.querySelector('dialog#settings .search_bar > input').focus()
}
})
new Action('import_settings', {
icon: 'folder',
category: 'blockbench',
click: function () {
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 = [];
var settings_copy = {}
for (var key in settings) {
settings_copy[key] = settings[key].value;
if (settings[key].value && settings[key].type == 'password') {
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];
})
}
resolve(result !== 2);
})
});
if (!go_on) return;
}
Blockbench.export({
resource_id: 'config',
type: 'Blockbench Settings',
extensions: ['bbsettings'],
content: compileJSON({settings: settings_copy})
})
}
})
BarItems.import_settings.toElement('#settings_title_bar')
BarItems.export_settings.toElement('#settings_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.structure[category]) {
Settings.structure[category] = {
name: tl('settings.category.'+category),
open: category === 'general',
items: {}
}
}
Settings.structure[category].items[key] = settings[key]
}
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 = '';
}
},
component: {
data() {return {
structure: Settings.structure,
open_category: 'general',
search_term: '',
}},
methods: {
saveSettings() {
Settings.saveLocalStorages();
},
getIconNode: Blockbench.getIconNode,
Condition
},
computed: {
list() {
if (this.search_term) {
var keywords = this.search_term.toLowerCase().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;
}
}
},
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="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();
}
})
})