const elements = []; const Outliner = { root: [], elements: elements, selected: selected, buttons: { visibility: { id: 'visibility', title: tl('switches.visibility'), icon: ' fa fa-eye', icon_off: ' fa fa-eye-slash', advanced_option: false, click: function(obj) { if (obj.locked) return; obj.toggle('visibility') } }, locked: { id: 'locked', title: tl('switches.lock'), icon: ' fas fa-lock', icon_off: ' fas fa-lock-open', advanced_option: true, click: function(obj) { if (obj.locked && Format.force_lock) return; obj.toggle('locked') updateSelection() } }, export: { id: 'export', title: tl('switches.export'), icon: ' fa fa-camera', icon_off: ' far fa-window-close', advanced_option: true, click: function(obj) { if (obj.locked) return; obj.toggle('export') } }, shading: { id: 'shading', get title() {return Project.box_uv ? tl('switches.mirror') : tl('switches.shading')}, get icon() {return Project.box_uv ? 'fa fa-star' : 'fa fa-star'}, get icon_off() {return Project.box_uv ? 'fas fa-star-half-alt' : 'far fa-star'}, advanced_option: true, click: function(obj) { if (obj.locked) return; obj.toggle('shade') Canvas.updateUVs() if (obj instanceof Cube && obj.visibility && !obj.selected) { Canvas.updateUV(obj); } } }, autouv: { id: 'autouv', title: tl('switches.autouv'), icon: ' fa fa-thumbtack', icon_off: ' far fa-times-circle', icon_alt: ' fa fa-magic', advanced_option: true, click: function(obj) { if (obj.locked) return; var state = obj.autouv+1 if (state > 2) state = 0 obj.toggle('autouv', state) } } } } //Colors var markerColors = [ {pastel: "#A2EBFF", standard: "#58C0FF", name: 'light_blue'}, {pastel: "#FFF899", standard: "#F3D81A", name: 'yellow'}, {pastel: "#E8BD7B", standard: "#EC9218", name: 'orange'}, {pastel: "#FFA7A4", standard: "#FA565D", name: 'red'}, {pastel: "#C5A6E8", standard: "#B55AF8", name: 'purple'}, {pastel: "#A6C8FF", standard: "#4D89FF", name: 'blue'}, {pastel: "#7BFFA3", standard: "#00CE71", name: 'green'}, {pastel: "#BDFFA6", standard: "#AFFF62", name: 'lime'} ] class OutlinerElement { constructor(uuid) { this.uuid = uuid || guid() this.export = true; this.locked = false; } init() { OutlinerElement.uuids[this.uuid] = this; this.constructor.all.safePush(this); return this; } //Sorting sortInBefore(element, index_mod) { var index = -1; index_mod = index_mod || 0; if (element.parent === 'root') { index = Outliner.root.indexOf(element) var arr = Outliner.root this.parent = 'root' } else { index = element.parent.children.indexOf(element) element = element.parent var arr = element.children this.parent = element } this.removeFromParent() //Adding if (index < 0) arr.push(this) else { arr.splice(index+index_mod, 0, this) } TickUpdates.outliner = true; return this; } addTo(group, index = -1) { //Resolve Group Argument if (group === undefined) { group = 'root' } else if (group !== 'root') { if (group.type !== 'group') { if (group.parent === 'root') { index = Outliner.root.indexOf(group)+1 group = 'root' } else { index = group.parent.children.indexOf(group)+1 group = group.parent } } } this.removeFromParent() //Get Array if (group === 'root') { var arr = Outliner.root this.parent = 'root' } else { var arr = group.children this.parent = group } //Adding if (arr.includes(this)) return this; if (index < 0) arr.push(this) else { arr.splice(index, 0, this) } //Loading TickUpdates.outliner = true; return this; } removeFromParent() { this.getParentArray().remove(this); return this; } getParentArray() { if (this.parent === 'root') { return Outliner.root } else if (typeof this.parent === 'object') { return this.parent.children } } //Outliner showInOutliner() { var scope = this; if (this.parent !== 'root') { this.parent.openUp() } Vue.nextTick(() => { var el = $('#'+scope.uuid) if (el.length === 0) return; var outliner_pos = $('#outliner').offset().top var el_pos = el.offset().top if (el_pos > outliner_pos && el_pos < $('#cubes_list').height() + outliner_pos) return; var multiple = el_pos > outliner_pos ? 0.8 : 0.2 var scroll_amount = el.offset().top + $('#cubes_list').scrollTop() - outliner_pos - 20 scroll_amount -= $('#cubes_list').height()*multiple - 15 $('#cubes_list').animate({ scrollTop: scroll_amount }, 200); }) } updateElement() { var scope = this; var old_name = this.name; scope.name = '_&/3%6-7A'; scope.name = old_name; return this; } getDepth() { var d = 0; function it(p) { if (p.parent) { d++; return it(p.parent) } else { return d-1; } } return it(this) } remove() { this.constructor.all.remove(this); if (OutlinerElement.uuids[this.uuid] == this) delete OutlinerElement.uuids[this.uuid]; this.removeFromParent() } rename() { this.showInOutliner() var obj = $('#'+this.uuid+' > div.outliner_object > input.cube_name') obj.attr('disabled', false) obj.select() obj.focus() obj.addClass('renaming') Blockbench.addFlag('renaming') this.old_name = this.name return this; } saveName(save) { var scope = this; if (save !== false && scope.name.trim().length > 0 && scope.name != scope.old_name) { var name = scope.name.trim(); scope.name = scope.old_name; if (scope.type === 'group') { Undo.initEdit({outliner: true}) } else { Undo.initEdit({elements: [scope]}) } scope.name = name scope.sanitizeName(); delete scope.old_name if (Condition(scope.needsUniqueName)) { scope.createUniqueName() } Undo.finishEdit('rename') } else { scope.name = scope.old_name delete scope.old_name } return this; } sanitizeName() { var name_regex = typeof this.name_regex == 'function' ? this.name_regex(this) : this.name_regex; if (name_regex) { var regex = new RegExp(`[^${name_regex}]`, 'g'); this.name = this.name.replace(regex, c => { if (c == '-' && '_'.search(regex) == -1) { return '_'; } if (c.toLowerCase().search(regex) == -1) { return c.toLowerCase(); } return ''; }); } } createUniqueName(arr) { if (!Condition(this.needsUniqueName)) return; var scope = this; var others = this.constructor.all.slice(); if (arr && arr.length) { arr.forEach(g => { others.safePush(g) }) } let zero_based = this.name.match(/[^\d]0$/) !== null; var name = this.name.replace(/\d+$/, '').replace(/\s+/g, '_'); function check(n) { for (var i = 0; i < others.length; i++) { if (others[i] !== scope && others[i].name.toLowerCase() == n.toLowerCase()) return false; } return true; } if (check(this.name)) { return this.name; } for (var num = zero_based ? 1 : 2; num < 8e3; num++) { if (check(name+num)) { scope.name = name+num; return scope.name; } } return false; } isIconEnabled(btn) { switch (btn.id) { case 'visibility': return this.visibility break; case 'export': return this.export break; case 'locked': return this.locked break; case 'shading': return this.shade break; case 'autouv': if (!this.autouv) { return false } else if (this.autouv === 1) { return true } else { return 'alt' } break; } return true; } isChildOf(group, max_levels) { function iterate(obj, level) { if (!obj || obj === 'root') { return false; } else if (obj === group) { return true; } else if (!max_levels || level < max_levels-1) { return iterate(obj.parent, level+1) } return false; } return iterate(this.parent, 0) } get mirror_uv() { return !this.shade; } set mirror_uv(val) { this.shade = !val; } } OutlinerElement.uuids = {}; class NonGroup extends OutlinerElement { constructor(data, uuid) { super(uuid); this.parent = 'root'; this.selected = false; } init() { super.init(); elements.safePush(this); } remove() { super.remove() selected.remove(this); elements.remove(this); this.constructor.selected.remove(this); return this; } showContextMenu(event) { Prop.active_panel = 'outliner' if (this.locked) return this; if (!this.selected) { this.select() } this.menu.open(event, this) return this; } forSelected(fc, undo_tag) { if (this.constructor.selected.length <= 1 || !this.constructor.selected.includes(this)) { var edited = [this] } else { var edited = this.constructor.selected } if (typeof fc === 'function') { if (undo_tag) { Undo.initEdit({elements: edited}) } for (var i = 0; i < edited.length; i++) { fc(edited[i]) } if (undo_tag) { Undo.finishEdit(undo_tag) } } return edited; } toggle(key, val) { if (val === undefined) { var val = !this[key] } this.forSelected((el) => { el[key] = val }, 'toggle '+key) if (key === 'visibility') { Canvas.updateVisibility() } return this; } duplicate() { var copy = new this.constructor(this); //Numberation var number = copy.name.match(/[0-9]+$/) if (number) { number = parseInt(number[0]) copy.name = copy.name.split(number).join(number+1) } //Rest copy.sortInBefore(this, 1).init() var index = selected.indexOf(this) if (index >= 0) { selected[index] = copy } else { selected.push(copy) } if (Condition(copy.needsUniqueName)) { copy.createUniqueName() } TickUpdates.outliner = true; TickUpdates.selection = true; return copy; } select(event, isOutlinerClick) { var scope = this; if (scope === undefined || Modes.animate) return false; //Shiftv var just_selected = [] if (event && event.shiftKey === true && scope.getParentArray().includes(selected[selected.length-1]) && !Modes.paint && isOutlinerClick) { var starting_point; var last_selected = selected[selected.length-1] scope.getParentArray().forEach(function(s, i) { if (s === last_selected || s === scope) { if (starting_point) { starting_point = false } else { starting_point = true } if (s.type !== 'group') { if (!selected.includes(s)) { s.selectLow() just_selected.push(s) } } else { s.selectLow() } } else if (starting_point) { if (s.type !== 'group') { if (!selected.includes(s)) { s.selectLow() just_selected.push(s) } } else { s.selectLow() } } }) //Control } else if (event && !Modes.paint && (event.ctrlOrCmd || event.shiftKey )) { if (selected.includes(scope)) { selected = selected.filter(function(e) { return e !== scope }) } else { scope.selectLow() just_selected.push(scope) } //Normal } else { selected.forEachReverse(obj => obj.unselect()) if (Group.selected) Group.selected.unselect() scope.selectLow() just_selected.push(scope) scope.showInOutliner() } if (Group.selected) { Group.selected.unselect() } Group.all.forEach(function(s) { s.selected = false; }) Blockbench.dispatchEvent('added_to_selection', {added: just_selected}) updateSelection() return this; } selectLow() { selected.safePush(this); this.constructor.selected.safePush(this) this.selected = true; TickUpdates.selection = true; return this; } unselect() { selected.remove(this); this.selected = false; this.constructor.selected.remove(this); TickUpdates.selection = true; return this; } } NonGroup.prototype.isParent = false; NonGroup.fromSave = function(obj, keep_uuid) { switch (obj.type) { case 'locator': return new Locator(obj, keep_uuid ? obj.uuid : 0).init() break; case 'cube': default: return new Cube(obj, keep_uuid ? obj.uuid : 0).init() break; } } NonGroup.selected = selected; NonGroup.all = elements; Array.prototype.findRecursive = function(key1, val) { var i = 0 while (i < this.length) { if (this[i][key1] === val) { return this[i]; } else if (this[i].children && this[i].children.length > 0) { let inner = this[i].children.findRecursive(key1, val) if (inner !== undefined) { return inner; } } i++; } return undefined; } function forOutlinerSelection(item, cb) { if (selected.length > 1 && selected.includes(item)) { var items = selected } else { var items = [item] } items.forEach(function(item) { cb(item) }) } function getAllOutlinerObjects() { var ta = [] function iterate(array) { var i = 0; while (i < array.length) { ta.push(array[i]) if (array[i].children && array[i].children.length > 0) { iterate(array[i].children) } i++; } } iterate(Outliner.root) return ta; } function compileGroups(undo, lut) { var result = [] function iterate(array, save_array) { var i = 0; for (var element of array) { if (element.type === 'group') { if (lut === undefined || element.export === true) { var obj = element.compile(undo) if (element.children.length > 0) { iterate(element.children, obj.children) } save_array.push(obj) } } else { if (undo) { save_array.push(element.uuid) } else { if (lut) { var index = lut[elements.indexOf(element)] } else { var index = elements.indexOf(element) } if (index >= 0) { save_array.push(index) } } } i++; } } iterate(Outliner.root, result) return result; } function parseGroups(array, importGroup, startIndex) { function iterate(array, save_array, addGroup) { var i = 0; while (i < array.length) { if (typeof array[i] === 'number' || typeof array[i] === 'string') { if (typeof array[i] === 'number') { var obj = elements[array[i] + (startIndex ? startIndex : 0) ] } else { var obj = elements.findRecursive('uuid', array[i]) } if (obj) { obj.removeFromParent() save_array.push(obj) obj.parent = addGroup if (Blockbench.hasFlag('importing') && typeof addGroup === 'object') { if (obj instanceof Cube) { if (addGroup.autouv !== undefined) { obj.autouv = addGroup.autouv if (obj.autouv === true) obj.autouv = 1 if (obj.autouv === false) obj.autouv = 0 } if (addGroup.visibility !== undefined) { obj.visibility = addGroup.visibility } } } } } else { var obj = new Group(array[i], array[i].uuid) obj.parent = addGroup obj.isOpen = !!array[i].isOpen if (array[i].uuid) { obj.uuid = array[i].uuid } save_array.push(obj) obj.init() if (array[i].children && array[i].children.length > 0) { iterate(array[i].children, obj.children, obj) } if (array[i].content && array[i].content.length > 0) { iterate(array[i].content, obj.children, obj) } } i++; } } if (importGroup && startIndex !== undefined) { iterate(array, importGroup.children, importGroup) } else { Outliner.root.length = 1; Outliner.root.splice(0, 1); Group.all.empty(); iterate(array, Outliner.root, 'root'); } } //Outliner function loadOutlinerDraggable() { function getOrder(loc, obj) { if (!obj) { return; } else if (obj instanceof Group) { if (loc < 8) return -1; if (loc > 24) return 1; } else { if (loc < 16) return -1; return 1; } return 0; } Vue.nextTick(function() { $('li.outliner_node:not(.ui-droppable) > div.outliner_object').draggable({ delay: 120, revertDuration: 50, revert: 'invalid', appendTo: 'body', zIndex: 19, cursorAt: {left: 5}, start(event, ui) { if (event.target && event.target.parentNode) { var element = Outliner.root.findRecursive('uuid', event.target.parentNode.id) if (!element || element.locked) return false; } }, helper: function() { var item = Outliner.root.findRecursive('uuid', $(this).attr('id')) var helper = $(this).clone() if (selected.length > 1) { helper.append('