2022-05-15 02:37:35 +08:00
|
|
|
Vue.component('search-bar', {
|
|
|
|
props: {
|
|
|
|
value: String,
|
|
|
|
hide: Boolean
|
|
|
|
},
|
|
|
|
data() {return {
|
|
|
|
hidden: this.hide
|
|
|
|
}},
|
|
|
|
methods: {
|
|
|
|
change(text) {
|
|
|
|
this.$emit('input', text)
|
|
|
|
},
|
|
|
|
clickIcon() {
|
|
|
|
if (this.hide && !this.value) {
|
|
|
|
this.hidden = false;
|
|
|
|
this.$refs.input.focus();
|
|
|
|
} else {
|
|
|
|
this.value = '';
|
|
|
|
this.$emit('input', '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<div class="search_bar" :class="{folded: (!value && hidden)}">
|
2023-02-18 17:18:50 +08:00
|
|
|
<input type="text" inputmode="search" ref="input" class="dark_bordered" :value="value" @focusout="hidden = hide;" @input="change($event.target.value)">
|
2022-05-15 02:37:35 +08:00
|
|
|
<i class="material-icons" :class="{light_on_hover: !!value}" @click="clickIcon()">{{ value ? 'clear' : 'search' }}</i>
|
|
|
|
</div>`
|
|
|
|
})
|
|
|
|
|
|
|
|
Vue.component('select-input', {
|
|
|
|
props: {
|
|
|
|
value: String,
|
2022-12-31 05:17:34 +08:00
|
|
|
options: Object,
|
|
|
|
custom_dropdown: Function
|
2022-05-15 02:37:35 +08:00
|
|
|
},
|
|
|
|
data() {return {
|
|
|
|
id: bbuid(8)
|
|
|
|
}},
|
|
|
|
methods: {
|
|
|
|
set(value) {
|
|
|
|
this.value = value;
|
|
|
|
this.$emit('input', value);
|
|
|
|
},
|
|
|
|
getNameFor(key) {
|
|
|
|
let val = this.options[key];
|
|
|
|
if (val) {
|
|
|
|
return tl(val.name || val);
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
open(event) {
|
|
|
|
if (Menu.closed_in_this_click == this.id) return this;
|
|
|
|
let items = [];
|
2022-12-31 05:17:34 +08:00
|
|
|
if (typeof this.custom_dropdown == 'function') {
|
|
|
|
items = this.custom_dropdown(event, (value) => this.set(value));
|
|
|
|
} else {
|
|
|
|
for (let key in this.options) {
|
|
|
|
let val = this.options[key];
|
|
|
|
if (val) {
|
|
|
|
items.push({
|
|
|
|
name: this.getNameFor(key),
|
|
|
|
icon: val.icon || ((this.value == key) ? 'far.fa-dot-circle' : 'far.fa-circle'),
|
|
|
|
condition: val.condition,
|
|
|
|
click: (e) => {
|
|
|
|
this.set(key);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2022-05-15 02:37:35 +08:00
|
|
|
}
|
|
|
|
}
|
2022-05-19 05:52:12 +08:00
|
|
|
let menu = new Menu(this.id, items, {searchable: items.length > 16});
|
2022-05-15 02:37:35 +08:00
|
|
|
menu.node.style['min-width'] = this.$el.clientWidth+'px';
|
|
|
|
menu.open(event.target, this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<bb-select @click="open($event)">
|
|
|
|
{{ getNameFor(value) }}
|
|
|
|
</bb-select>
|
|
|
|
`
|
2023-04-05 02:16:51 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
Vue.component('numeric-input', {
|
|
|
|
props: {
|
|
|
|
value: Number,
|
|
|
|
min: Number,
|
|
|
|
max: Number,
|
|
|
|
step: Number,
|
|
|
|
},
|
|
|
|
data() {return {
|
|
|
|
string_value: (this.value||0).toString(),
|
|
|
|
resolved_value: (this.value||0)
|
|
|
|
}},
|
|
|
|
watch: {
|
|
|
|
value(v) {
|
|
|
|
if (this.resolved_value != v) {
|
|
|
|
this.string_value = trimFloatNumber(v, 10);
|
|
|
|
this.resolved_value = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
change(value) {
|
|
|
|
this.string_value = typeof value == 'number' ? trimFloatNumber(value) : value;
|
|
|
|
this.resolved_value = Math.clamp(NumSlider.MolangParser.parse(this.string_value), this.min, this.max);
|
|
|
|
this.$emit('input', this.resolved_value);
|
|
|
|
},
|
|
|
|
slide(e1) {
|
|
|
|
convertTouchEvent(e1);
|
|
|
|
let last_difference = 0;
|
|
|
|
let move = e2 => {
|
|
|
|
convertTouchEvent(e2);
|
|
|
|
let difference = Math.trunc((e2.clientX - e1.clientX) / 10) * (this.step || 1);
|
|
|
|
if (difference != last_difference) {
|
|
|
|
let value = Math.clamp((parseFloat(this.value) || 0) + (difference - last_difference), this.min, this.max);
|
|
|
|
this.change(value);
|
|
|
|
last_difference = difference;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let stop = e2 => {
|
|
|
|
removeEventListeners(document, 'mousemove touchmove', move);
|
|
|
|
removeEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', move);
|
|
|
|
addEventListeners(document, 'mouseup touchend', stop);
|
|
|
|
},
|
|
|
|
resolve() {
|
|
|
|
this.string_value = trimFloatNumber(this.resolved_value, 10);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `
|
|
|
|
<div class="numeric_input">
|
|
|
|
<input class="dark_bordered focusable_input" :value="string_value" @input="change($event.target.value)" inputmode="decimal" lang="en" @focusout="resolve($event)" @dblclick="resolve($event)">
|
|
|
|
<div class="tool numeric_input_slider" @mousedown="slide($event)" @touchstart="slide($event)"><i class="material-icons">code</i></div>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
})
|
2023-06-23 04:35:01 +08:00
|
|
|
Vue.component('dynamic-icon', {
|
|
|
|
props: {
|
|
|
|
icon: String,
|
|
|
|
color: String,
|
|
|
|
},
|
|
|
|
render(h) {
|
|
|
|
let node = Blockbench.getIconNode(this.icon, this.color);
|
2023-11-29 03:52:23 +08:00
|
|
|
let attrs = {
|
|
|
|
class: node.className,
|
|
|
|
attrs: {
|
|
|
|
src: node.attributes.src?.value
|
|
|
|
},
|
|
|
|
style: {
|
|
|
|
color: node.style.color
|
|
|
|
},
|
|
|
|
on: this.$listeners
|
|
|
|
};
|
|
|
|
return h(node.tagName, attrs, node.textContent);
|
2023-06-23 04:35:01 +08:00
|
|
|
}
|
|
|
|
})
|