blockbench/js/interface/action_control.js
2023-10-22 20:44:45 +02:00

426 lines
12 KiB
JavaScript

const ActionControl = {
get open() {return ActionControl.vue._data.open},
set open(state) {ActionControl.vue._data.open = !!state},
type: 'action_selector',
max_length: 32,
select(input) {
ActionControl.open = true;
open_interface = ActionControl;
ActionControl.vue._data.index = 0;
ActionControl.vue.updateSearch();
if (input) {
ActionControl.vue.search_input = input;
}
Vue.nextTick(_ => {
let element = $('#action_selector > input');
element.trigger('focus');
if (!input) element.trigger('select');
})
for (let key in settings) {
let setting = settings[key];
if (setting.type == 'toggle') {
setting.icon = setting.value ? 'check_box' : 'check_box_outline_blank';
}
}
},
show(...args) {
return this.select(...args);
},
hide() {
if (open_interface == ActionControl) open_interface = false;
ActionControl.recent_in_streamer_mode = false;
ActionControl.open = false;
},
confirm(e) {
var data = ActionControl.vue._data
var action = data.list[data.index]
ActionControl.hide()
if (action) {
ActionControl.trigger(action, e)
}
},
cancel() {
ActionControl.hide()
},
trigger(action, e) {
if (action.id == 'action_control') {
$('body').effect('shake');
Blockbench.showQuickMessage('Congratulations! You have discovered recursion!', 3000)
}
if (action instanceof BarSelect) {
action.open({target: ActionControl.vue.$el.childNodes[2]});
} else if (action.type == 'recent_project') {
Blockbench.read([action.description], {}, files => {
loadModelFile(files[0]);
})
} else if (action.type == 'project_tab') {
ModelProject.all.find(p => p.uuid == action.uuid).select();
} else if (action.type == 'profile') {
let profile = SettingsProfile.all.find(p => p.uuid == action.uuid);
if (profile) {
profile.select();
} else {
SettingsProfile.unselect();
}
} else if (action.type == 'plugin') {
let plugin = Plugins.all.find(plugin => plugin.id == action.id);
if (plugin.installed) {
plugin.uninstall();
} else {
plugin.install();
}
} else {
action.trigger(e);
}
},
click(action, e) {
ActionControl.trigger(action, e)
ActionControl.hide()
},
handleKeys(e) {
var data = ActionControl.vue._data
if (e.altKey) {
ActionControl.vue.$forceUpdate()
}
function updateScroll() {
Vue.nextTick(() => {
let list = document.querySelector('#action_selector_list ul');
let node = list && list.children[data.index];
if (!node) return;
var list_pos = $(list).offset().top;
var el_pos = $(node).offset().top;
if (el_pos < list_pos) {
list.scrollTop += el_pos - list_pos;
} else if (el_pos > list.clientHeight + list_pos - 20) {
list.scrollTop += el_pos - (list.clientHeight + list_pos) + 30;
}
})
}
if (e.which === 38) {
data.index--;
if (data.index < 0) {
data.index = data.length-1;
}
updateScroll();
} else if (e.which === 40) {
data.index++;
if (data.index >= data.length) {
data.index = 0;
}
updateScroll();
} else {
return false;
}
return true;
}
}
BARS.defineActions(function() {
new Action('action_control', {
icon: 'play_arrow',
category: 'blockbench',
keybind: new Keybind({key: 'f'}),
click: function () {
ActionControl.select()
}
})
ActionControl.vue = new Vue({
el: '#action_selector',
data: {
open: false,
search_input: '',
index: 0,
length: 0,
search_types: {
'': {name: tl('action.action_control'), icon: 'play_arrow'},
'setting': {name: tl('data.setting'), icon: 'settings'},
'settings': {name: tl('data.setting'), icon: 'settings'},
'profile': {name: tl('data.settings_profile'), icon: 'manage_accounts'},
'+plugin': {name: tl('action.add_plugin'), icon: 'extension'},
'-plugin': {name: tl('action.remove_plugin'), icon: 'extension_off'},
'recent': {name: tl('menu.file.recent'), icon: 'history'},
'tab': {name: tl('menu.action_control.type.tab'), icon: 'view_stream'},
'angle': {name: tl('menu.action_control.type.angle'), icon: 'videocam'},
},
list: []
},
computed: {
search_type() {
if (this.search_input.search(/:/) > 0) {
let [type] = this.search_input.split(/:\s*(.*)/);
type = type.toLowerCase();
if (this.search_types[type]) {
return type;
}
}
return '';
}
},
methods: {
updateSearch() {
var list = this._data.list.empty();
var type = this.search_type.toLowerCase();
var search_input = this._data.search_input.toLowerCase()
search_input = search_input.replace(type+':', '').trim();
if (!type && search_input) {
for (let key in this.search_types) {
if (key == 'setting') continue;
if (key.includes(search_input)) {
list.push({
name: this.search_types[key].name,
icon: this.search_types[key].icon,
keybind_label: `${key}:`,
trigger: () => {
ActionControl.select(key && (key + ': '));
}
})
}
}
}
if (!type) {
for (let item of Keybinds.actions) {
if (
search_input.length == 0 ||
item.name.toLowerCase().includes(search_input) ||
item.id.toLowerCase().includes(search_input)
) {
if ((item instanceof Action || item instanceof BarSelect) && Condition(item.condition) && !item.linked_setting) {
list.safePush(item)
if (list.length > ActionControl.max_length) break;
}
}
}
}
if (!type || type == 'settings' || type == 'setting') {
if (list.length <= ActionControl.max_length) {
for (let key in settings) {
let setting = settings[key];
if (
search_input.length == 0 ||
setting.name.toLowerCase().includes(search_input) ||
key.toLowerCase().includes(search_input)
) {
if (Condition(setting.condition)) {
list.push(setting)
if (list.length > ActionControl.max_length) break;
}
}
}
}
}
if (isApp && type == 'recent') {
if (settings.streamer_mode.value && !ActionControl.recent_in_streamer_mode) {
list.push({
name: tl('menu.action_control.recent_in_streamer_mode'),
description: tl('menu.action_control.recent_in_streamer_mode.desc'),
icon: 'live_tv',
trigger() {
setTimeout(_ => {
ActionControl.select('recent: ');
ActionControl.recent_in_streamer_mode = true;
ActionControl.vue.updateSearch();
}, 1);
}
})
} else {
for (let project of recent_projects) {
if (
search_input.length == 0 ||
project.path.toLowerCase().includes(search_input)
) {
list.push({
name: project.name,
icon: project.icon,
description: settings.streamer_mode.value ? '' : project.path,
keybind_label: StartScreen.vue.getDate(project),
type: 'recent_project'
})
if (list.length > ActionControl.max_length) break;
}
}
}
}
if (type == 'tab') {
for (let project of ModelProject.all) {
if (
search_input.length == 0 ||
project.name.toLowerCase().includes(search_input) ||
project.geometry_name.toLowerCase().includes(search_input)
) {
list.push({
name: project.getDisplayName(),
icon: project.format.icon,
description: project.path,
keybind_label: Modes.options[project.mode].name,
uuid: project.uuid,
type: 'project_tab'
})
if (list.length > ActionControl.max_length) break;
}
}
}
if (type == 'profile') {
list.push({
name: tl('generic.none'),
icon: SettingsProfile.selected ? 'far.fa-circle' : 'far.fa-dot-circle',
type: 'profile'
})
for (let profile of SettingsProfile.all) {
if (profile.condition.type !== 'selectable') continue;
if (
search_input.length == 0 ||
profile.name.toLowerCase().includes(search_input)
) {
list.push({
name: profile.name,
icon: profile.selected ? 'far.fa-dot-circle' : 'far.fa-circle',
color: markerColors[profile.color].standard,
uuid: profile.uuid,
type: 'profile'
})
if (list.length > ActionControl.max_length) break;
}
}
}
if (type.substr(1) == 'plugin') {
for (let plugin of Plugins.all) {
if (
plugin.installed == (type[0] == '-') &&
(search_input.length == 0 ||
plugin.name.toLowerCase().includes(search_input) ||
plugin.description.toLowerCase().includes(search_input))
) {
let icon = plugin.icon;
if (plugin.hasImageIcon()) {
icon = document.createElement('img');
icon.classList.add('icon');
icon.src = plugin.getIcon();
}
list.push({
name: plugin.name,
icon,
description: plugin.description,
keybind_label: plugin.author,
id: plugin.id,
type: 'plugin'
})
if (list.length > ActionControl.max_length) break;
}
}
}
if (type == 'angle') {
let angles = Preview.prototype.menu.structure.find(m => m.id == 'angle').children(Preview.selected);
for (let angle of angles) {
if (typeof angle != 'object') continue;
let name = tl(angle.name);
if (
search_input.length == 0 ||
name.toLowerCase().includes(search_input) ||
(angle.id && angle.id.toLowerCase().includes(search_input))
) {
list.push({
name,
icon: angle.icon,
color: angle.color,
type: 'angle',
trigger() {
Preview.selected.loadAnglePreset(angle.preset);
}
})
if (list.length > ActionControl.max_length) break;
}
}
}
this._data.length = list.length;
if (this._data.index < 0) {
this._data.index = 0;
}
if (this._data.index >= list.length) {
this._data.index = list.length-1;
}
return list;
},
subtext() {
let action = this.list[this.index];
if (Pressing.alt) {
if (action instanceof Setting) {
if (action.type == 'select') {
return action.options[action.value];
} else {
return action.value;
}
} else {
action.keybind.label;
}
} else {
return action.description;
}
},
openTypeMenu() {
let items = [];
for (let key in this.search_types) {
if (key == 'setting') continue;
items.push({
name: this.search_types[key].name,
icon: this.search_types[key].icon,
click: () => {
this.search_input = key && (key + ': ');
Vue.nextTick(_ => {
let element = $('#action_selector > input');
element.trigger('focus');
})
}
});
}
new Menu(items).show(this.$refs.search_type_menu);
},
click: ActionControl.click,
getIconNode: Blockbench.getIconNode
},
watch: {
search_input() {
this.updateSearch();
}
},
template: `
<dialog id="action_selector" v-if="open">
<div class="tool" ref="search_type_menu" @click="openTypeMenu($event)">
<dynamic-icon :icon="search_types[search_type] ? search_types[search_type].icon : 'fullscreen'" />
</div>
<input type="text" v-model="search_input" inputmode="search" @input="e => search_input = e.target.value" autocomplete="off" autosave="off" autocorrect="off" spellcheck="false" autocapitalize="off">
<i class="material-icons" id="action_search_bar_icon" @click="search_input = ''">{{ search_input ? 'clear' : 'search' }}</i>
<div v-if="search_type" class="action_selector_type_overlay">{{ search_type }}:</div>
<div id="action_selector_list">
<ul>
<li v-for="(item, i) in list"
:class="{selected: i === index}"
:title="item.description"
@click="click(item, $event)"
@mouseenter="index = i"
>
<dynamic-icon :icon="item.icon" :color="item.color" />
<span>{{ item.name }}</span>
<label class="keybinding_label">{{ item.keybind_label || (item.keybind ? item.keybind.label : '') }}</label>
</li>
</ul>
<div class="small_text" v-if="list[index]">{{ subtext() }}</div>
</div>
</dialog>
`
})
})