From 46340a3dd88f18d1207ae4e843ed7c936355627d Mon Sep 17 00:00:00 2001 From: Jon Ziebell Date: Tue, 9 Aug 2022 09:05:34 -0400 Subject: [PATCH] Added undo/redo --- css/dashboard.css | 8 +- js/component/card/floor_plan_editor.js | 91 +++++++++-- js/component/floor_plan.js | 193 ++++++++++++++++++++++-- js/component/floor_plan_entity.js | 2 + js/component/floor_plan_entity/point.js | 12 +- js/component/floor_plan_entity/room.js | 55 ++++--- js/component/floor_plan_entity/wall.js | 13 +- 7 files changed, 324 insertions(+), 50 deletions(-) diff --git a/css/dashboard.css b/css/dashboard.css index a2d2491..bf505d4 100644 --- a/css/dashboard.css +++ b/css/dashboard.css @@ -443,6 +443,7 @@ input[type=radio] { .icon.cloud_question:before { content: "\F0A39"; } .icon.code_tags:before { content: "\F0174"; } .icon.cog:before { content: "\F0493"; } +.icon.delete:before { content: "\F01B4"; } .icon.dots_vertical:before { content: "\F01D9"; } .icon.download:before { content: "\F01DA"; } .icon.earth:before { content: "\F01E7"; } @@ -500,7 +501,10 @@ input[type=radio] { .icon.numeric_9_box:before { content: "\F03BC"; } .icon.open_in_new:before { content: "\F03CC"; } .icon.patreon:before { content: "\F0882"; } +.icon.pencil:before { content: "\F03EB"; } +.icon.plus:before { content: "\F0415"; } .icon.pound:before { content: "\F0423"; } +.icon.redo:before { content: "\F044E"; } .icon.refresh:before { content: "\F0450"; } .icon.resistor:before { content: "\F0B44"; } .icon.snowflake:before { content: "\F0717"; } @@ -509,6 +513,7 @@ input[type=radio] { .icon.thumb_up:before { content: "\F0513"; } .icon.tune:before { content: "\F062E"; } .icon.twitter:before { content: "\F0544"; } +.icon.undo:before { content: "\F054C"; } .icon.update:before { content: "\F06B0"; } .icon.vector_square_plus:before { content: "\F18DB"; } .icon.vector_square_remove:before { content: "\F18DC"; } @@ -530,9 +535,6 @@ input[type=radio] { .icon.wifi_strength_1_alert:before { content: "\F0920"; } .icon.wifi_strength_4:before { content: "\F0928"; } .icon.zigbee:before { content: "\F0D41"; } -.icon.pencil:before { content: "\F03EB"; } -.icon.plus:before { content: "\F0415"; } -.icon.delete:before { content: "\F01B4"; } .icon.f16:before { font-size: 16px; } .icon.f24:before { font-size: 24px; } diff --git a/js/component/card/floor_plan_editor.js b/js/component/card/floor_plan_editor.js index 3c88317..d91e6f5 100644 --- a/js/component/card/floor_plan_editor.js +++ b/js/component/card/floor_plan_editor.js @@ -8,6 +8,7 @@ beestat.component.card.floor_plan_editor = function(thermostat_id) { this.thermostat_id_ = thermostat_id; var change_function = beestat.debounce(function() { + // todo replace these with (if entity set active false?) delete self.state_.active_group; delete self.state_.active_room; @@ -71,10 +72,42 @@ beestat.component.card.floor_plan_editor.prototype.decorate_contents_ = function center_container.appendChild(get_started_button); } else { const floor_plan = beestat.cache.floor_plan[beestat.setting('floor_plan_id')]; - if (this.state_.active_group === undefined) { - this.state_.active_group = floor_plan.data.groups[0]; + + // Set group ids if they are not set. + floor_plan.data.groups.forEach(function(group) { + if (group.group_id === undefined) { + group.group_id = window.crypto.randomUUID(); + } + }); + + /** + * If there is an active_group_id, override whatever the current active + * group is. Used for undo/redo. + */ + if (this.state_.active_group_id !== undefined) { + for (let i = 0; i < floor_plan.data.groups.length; i++) { + if (floor_plan.data.groups[i].group_id === this.state_.active_group_id) { + this.state_.active_group = floor_plan.data.groups[i]; + delete this.state_.active_group_id; + break; + } + } } + // If there is no active group, set it to best guess of ground floor. + if (this.state_.active_group === undefined) { + let closest_distance = Infinity; + let closest_group; + floor_plan.data.groups.forEach(function(group) { + if (Math.abs(group.elevation) < closest_distance) { + closest_group = group; + closest_distance = Math.abs(group.elevation); + } + }); + this.state_.active_group = closest_group; + } + + // Decorate everything. const drawing_pane_container = $.createElement('div'); drawing_pane_container.style({ 'position': 'relative', @@ -135,7 +168,14 @@ beestat.component.card.floor_plan_editor.prototype.decorate_drawing_pane_ = func self.update_floor_plan_(); self.rerender(); }); - this.floor_plan_.addEventListener('clear_room', self.rerender.bind(this)); + this.floor_plan_.addEventListener('undo', function() { + self.update_floor_plan_(); + self.rerender(); + }); + this.floor_plan_.addEventListener('redo', function() { + self.update_floor_plan_(); + self.rerender(); + }); this.floor_plan_.addEventListener('change_group', self.rerender.bind(this)); // Add all of the entities to the SVG. @@ -170,17 +210,28 @@ beestat.component.card.floor_plan_editor.prototype.decorate_drawing_pane_ = func // Update GUI when a room is selected. room_entity.addEventListener('activate', function() { self.floor_plan_.update_infobox(); + self.floor_plan_.update_toolbar(); + self.update_info_pane_(); + }); + + // Update GUI when a room is deselected. + room_entity.addEventListener('inactivate', function() { + self.floor_plan_.update_infobox(); + self.floor_plan_.update_toolbar(); self.update_info_pane_(); }); // Activate the currently active room (mostly for rerenders). - if (room === self.state_.active_room) { + if ( + self.state_.active_room_entity !== undefined && + room.room_id === self.state_.active_room_entity.get_room().room_id + ) { room_entity.set_active(true); - } else { - // Render the room and save to the list of current entities. - room_entity.render(self.floor_plan_.get_g()); - self.entities_.room.push(room_entity); } + + // Render the room and save to the list of current entities. + room_entity.render(self.floor_plan_.get_g()); + self.entities_.room.push(room_entity); }); /** @@ -489,6 +540,8 @@ beestat.component.card.floor_plan_editor.prototype.get_subtitle_ = function() { * only run so fast. */ beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function() { + const self = this; + window.clearTimeout(this.update_timeout_); this.update_timeout_ = window.setTimeout(function() { new beestat.api() @@ -498,7 +551,7 @@ beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function { 'attributes': { 'floor_plan_id': beestat.setting('floor_plan_id'), - 'data': beestat.cache.floor_plan[beestat.setting('floor_plan_id')].data + 'data': self.get_floor_plan_data_(beestat.setting('floor_plan_id')) } }, 'update_floor_plan' @@ -507,6 +560,25 @@ beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function }, 1000); }; +/** + * Get floor plan data with UUIDs stripped. + * + * @param {number} floor_plan_id Floor plan ID + * + * @return {object} The modified floor plan data. + */ +beestat.component.card.floor_plan_editor.prototype.get_floor_plan_data_ = function(floor_plan_id) { + const floor_plan = beestat.cache.floor_plan[floor_plan_id]; + const data = beestat.clone(floor_plan.data); + data.groups.forEach(function(group) { + delete group.group_id; + group.rooms.forEach(function(room) { + delete room.room_id; + }); + }); + return data; +}; + /** * Decorate the menu. * @@ -557,7 +629,6 @@ beestat.component.card.floor_plan_editor.prototype.decorate_top_right_ = functio ).render(); })); } - } menu.add_menu_item(new beestat.component.menu_item() diff --git a/js/component/floor_plan.js b/js/component/floor_plan.js index 716c102..e82d666 100644 --- a/js/component/floor_plan.js +++ b/js/component/floor_plan.js @@ -132,7 +132,12 @@ beestat.component.floor_plan.prototype.render = function(parent) { e.key.toLowerCase() === 'z' && e.ctrlKey === true ) { - console.log('undo'); + self.undo_(); + } else if ( + e.key.toLowerCase() === 'y' && + e.ctrlKey === true + ) { + self.redo_(); } else if ( e.key === 'ArrowLeft' || e.key === 'ArrowRight' || @@ -150,16 +155,16 @@ beestat.component.floor_plan.prototype.render = function(parent) { switch (e.key) { case 'ArrowLeft': - entity.set_xy(x === null ? null : x - 1, y); + entity.set_xy(x === null ? null : x - 1, y, 'update'); break; case 'ArrowRight': - entity.set_xy(x === null ? null : x + 1, y); + entity.set_xy(x === null ? null : x + 1, y, 'update'); break; case 'ArrowUp': - entity.set_xy(x, y === null ? null : y - 1); + entity.set_xy(x, y === null ? null : y - 1, 'update'); break; case 'ArrowDown': - entity.set_xy(x, y === null ? null : y + 1); + entity.set_xy(x, y === null ? null : y + 1, 'update'); break; } } @@ -492,6 +497,48 @@ beestat.component.floor_plan.prototype.update_toolbar = function() { .addEventListener('click', this.toggle_snapping_.bind(this)) ); + // Undo + const undo_button = new beestat.component.tile() + .set_icon('undo') + .set_title('Undo [Ctrl+Z]') + .set_background_color(beestat.style.color.bluegray.base); + this.button_group_.add_button(undo_button); + + if ( + this.can_undo_() === true + ) { + undo_button + .set_background_hover_color(beestat.style.color.bluegray.light) + .set_text_color(beestat.style.color.gray.light) + .addEventListener('click', function() { + self.undo_(); + }); + } else { + undo_button + .set_text_color(beestat.style.color.bluegray.dark); + } + + // Redo + const redo_button = new beestat.component.tile() + .set_icon('redo') + .set_title('redo [Ctrl+Y]') + .set_background_color(beestat.style.color.bluegray.base); + this.button_group_.add_button(redo_button); + + if ( + this.can_redo_() === true + ) { + redo_button + .set_background_hover_color(beestat.style.color.bluegray.light) + .set_text_color(beestat.style.color.gray.light) + .addEventListener('click', function() { + self.redo_(); + }); + } else { + redo_button + .set_text_color(beestat.style.color.bluegray.dark); + } + // Zoom in const zoom_in_button = new beestat.component.tile() .set_icon('magnify_plus_outline') @@ -624,12 +671,15 @@ beestat.component.floor_plan.prototype.toggle_snapping_ = function() { * @param {object} room Optional room to copy from. */ beestat.component.floor_plan.prototype.add_room_ = function(room) { + this.save_buffer(); + const svg_view_box = this.view_box_; let new_room; if (room === undefined) { const new_room_size = 120; new_room = { + 'room_id': window.crypto.randomUUID(), 'x': svg_view_box.x + (svg_view_box.width / 2) - (new_room_size / 2), 'y': svg_view_box.y + (svg_view_box.height / 2) - (new_room_size / 2), 'points': [ @@ -665,6 +715,7 @@ beestat.component.floor_plan.prototype.add_room_ = function(room) { }); new_room = { + 'room_id': window.crypto.randomUUID(), 'x': svg_view_box.x + (svg_view_box.width / 2) - ((max_x - min_x) / 2), 'y': svg_view_box.y + (svg_view_box.height / 2) - ((max_y - min_y) / 2), 'points': beestat.clone(room.points) @@ -672,11 +723,10 @@ beestat.component.floor_plan.prototype.add_room_ = function(room) { } this.state_.active_group.rooms.push(new_room); - this.state_.active_room = new_room; - - if (this.state_.active_point_entity !== undefined) { - this.state_.active_point_entity.set_active(false); - } + new beestat.component.floor_plan_entity.room(this, this.state_) + .set_room(new_room) + .set_group(this.state_.active_group) + .set_active(true); this.dispatchEvent('add_room'); }; @@ -685,6 +735,8 @@ beestat.component.floor_plan.prototype.add_room_ = function(room) { * Remove the currently active room. */ beestat.component.floor_plan.prototype.remove_room_ = function() { + this.save_buffer(); + const self = this; const index = this.state_.active_group.rooms.findIndex(function(active_room) { @@ -718,13 +770,14 @@ beestat.component.floor_plan.prototype.clear_room_ = function() { if (this.state_.active_point_entity !== undefined) { this.state_.active_point_entity.set_active(false); } - this.dispatchEvent('clear_room'); }; /** * Remove the currently active point. */ beestat.component.floor_plan.prototype.remove_point_ = function() { + this.save_buffer(); + if (this.state_.active_room.points.length > 3) { for (let i = 0; i < this.state_.active_room.points.length; i++) { if (this.state_.active_point === this.state_.active_room.points[i]) { @@ -934,3 +987,121 @@ beestat.component.floor_plan.prototype.center_content = function() { this.update_view_box_(); } }; + +/** + * Save the current state to the undo/redo buffer. + * + * @param {boolean} clear Whether or not to allow clearing future buffer + * entries. + */ +beestat.component.floor_plan.prototype.save_buffer = function(clear = true) { + const buffer_size = 1000; + + if (this.state_.buffer === undefined) { + this.state_.buffer = []; + this.state_.buffer_pointer = 0; + } + + // If the buffer pointer is not at the end, clear those out. + if ( + clear === true && + this.state_.buffer_pointer !== this.state_.buffer.length + 1 + ) { + this.state_.buffer.length = this.state_.buffer_pointer; + } + + this.state_.buffer.push({ + 'floor_plan': beestat.clone(beestat.cache.floor_plan[beestat.setting('floor_plan_id')]), + 'active_room_entity': this.state_.active_room_entity, + 'active_group_id': this.state_.active_group.group_id + }); + + // If the buffer gets too long shrink it. + if (this.state_.buffer.length > buffer_size) { + this.state_.buffer.shift(); + } + + /** + * Update the buffer pointer. It always points at the index where the next + * buffer write will happen. + */ + this.state_.buffer_pointer = this.state_.buffer.length; + + this.update_toolbar(); +}; + +/** + * Undo + */ +beestat.component.floor_plan.prototype.undo_ = function() { + if (this.can_undo_() === true) { + /** + * When undoing, first save the buffer if the pointer is at the end to + * capture the current state then shift the buffer pointer back an extra. + */ + if (this.state_.buffer_pointer === this.state_.buffer.length) { + this.save_buffer(false); + this.state_.buffer_pointer--; + } + + // Decrement buffer pointer back to the previous row. + this.state_.buffer_pointer--; + + // Restore the floor plan. + beestat.cache.floor_plan[this.floor_plan_id_] = + beestat.clone(this.state_.buffer[this.state_.buffer_pointer].floor_plan); + + // Restore any active room. + this.state_.active_room_entity = + this.state_.buffer[this.state_.buffer_pointer].active_room_entity; + + // Restore any active group. + this.state_.active_group_id = + this.state_.buffer[this.state_.buffer_pointer].active_group_id; + + this.update_toolbar(); + this.dispatchEvent('undo'); + } +}; + +/** + * Whether or not you can undo. + * + * @return {boolean} + */ +beestat.component.floor_plan.prototype.can_undo_ = function() { + return this.state_.buffer_pointer > 0; +}; + +/** + * Redo + */ +beestat.component.floor_plan.prototype.redo_ = function() { + if (this.can_redo_() === true) { + this.state_.buffer_pointer++; + // Restore the floor plan. + beestat.cache.floor_plan[this.floor_plan_id_] = + beestat.clone(this.state_.buffer[this.state_.buffer_pointer].floor_plan); + + // Restore any active room. + this.state_.active_room_entity = + this.state_.buffer[this.state_.buffer_pointer].active_room_entity; + + // Restore any active group. + this.state_.active_group_id = + this.state_.buffer[this.state_.buffer_pointer].active_group_id; + + this.update_toolbar(); + this.dispatchEvent('redo'); + } +}; + +/** + * Whether or not you can redo. + * + * @return {boolean} + */ +beestat.component.floor_plan.prototype.can_redo_ = function() { + return this.state_.buffer !== undefined && + this.state_.buffer_pointer + 1 < this.state_.buffer.length; +}; diff --git a/js/component/floor_plan_entity.js b/js/component/floor_plan_entity.js index a663af6..646d57e 100644 --- a/js/component/floor_plan_entity.js +++ b/js/component/floor_plan_entity.js @@ -189,6 +189,7 @@ beestat.component.floor_plan_entity.prototype.mousemove_handler_ = function(e) { if (this.dragged_ === false) { this.dispatchEvent('drag_start'); this.dragged_ = true; + this.floor_plan_.save_buffer(); } this.after_mousemove_handler_(e); @@ -218,6 +219,7 @@ beestat.component.floor_plan_entity.prototype.mouseup_handler_ = function(e) { // If the mouse was actually moved at all then fire the drag stop event. if (this.dragged_ === true) { this.dispatchEvent('drag_stop'); + this.dispatchEvent('update'); } this.after_mouseup_handler_(e); diff --git a/js/component/floor_plan_entity/point.js b/js/component/floor_plan_entity/point.js index 6f62465..839d18a 100644 --- a/js/component/floor_plan_entity/point.js +++ b/js/component/floor_plan_entity/point.js @@ -15,7 +15,6 @@ beestat.extend(beestat.component.floor_plan_entity.point, beestat.component.floo */ beestat.component.floor_plan_entity.point.prototype.decorate_ = function(parent) { this.decorate_rect_(parent); - this.set_draggable_(true); }; @@ -125,10 +124,15 @@ beestat.component.floor_plan_entity.point.prototype.set_room = function(room) { * * @param {number} x The x position of this entity. * @param {number} y The y position of this entity. + * @param {string} event Optional event to fire when done. * * @return {beestat.component.floor_plan_entity.point} This. */ -beestat.component.floor_plan_entity.point.prototype.set_xy = function(x, y) { +beestat.component.floor_plan_entity.point.prototype.set_xy = function(x, y, event = 'lesser_update') { + if (event === 'update') { + this.floor_plan_.save_buffer(); + } + let clamped_x = x + this.room_.get_x(); let clamped_y = y + this.room_.get_y(); @@ -146,7 +150,7 @@ beestat.component.floor_plan_entity.point.prototype.set_xy = function(x, y) { this.update_rect_(); - this.dispatchEvent('update'); + this.dispatchEvent(event); return this; }; @@ -349,6 +353,8 @@ beestat.component.floor_plan_entity.point.prototype.set_active = function(active } else { delete this.state_.active_point; delete this.state_.active_point_entity; + + this.dispatchEvent('inactivate'); } if (this.rendered_ === true) { diff --git a/js/component/floor_plan_entity/room.js b/js/component/floor_plan_entity/room.js index 6e5f6ed..ef5db18 100644 --- a/js/component/floor_plan_entity/room.js +++ b/js/component/floor_plan_entity/room.js @@ -111,18 +111,17 @@ beestat.component.floor_plan_entity.prototype.decorate_points_ = function(parent .render(parent); // Update when a point is moved - point_entity.addEventListener('update', function() { + point_entity.addEventListener('lesser_update', function() { self.update_polygon_(); self.update_walls_(); - self.dispatchEvent('update'); }); // When a point is done moving normalize the points - point_entity.addEventListener('drag_stop', function() { + point_entity.addEventListener('update', function() { self.normalize_points_(); self.update_points_(); - self.update_walls_(); self.update_polygon_(); + self.update_walls_(); self.update_snap_points_(); self.dispatchEvent('update'); }); @@ -174,25 +173,16 @@ beestat.component.floor_plan_entity.prototype.decorate_walls_ = function(parent) .render(parent); self.walls_.push(wall_entity); - wall_entity.addEventListener('update', function() { + wall_entity.addEventListener('lesser_update', function() { self.update_polygon_(); self.update_points_(); self.update_walls_(); - self.dispatchEvent('update'); }); - // Clear any active points on drag start. - wall_entity.addEventListener('drag_start', function() { - if (self.active_point_entity_ !== undefined) { - self.active_point_entity_.set_active(false); - delete self.active_point_entity_; - } - }); - - wall_entity.addEventListener('drag_stop', function() { + wall_entity.addEventListener('update', function() { self.normalize_points_(); - self.update_polygon_(); self.update_points_(); + self.update_polygon_(); self.update_walls_(); self.update_snap_points_(); self.dispatchEvent('update'); @@ -240,6 +230,20 @@ beestat.component.floor_plan_entity.prototype.update_walls_ = function() { * @return {beestat.component.floor_plan_entity.room} This. */ beestat.component.floor_plan_entity.room.prototype.set_active = function(active) { + /** + * Always clear the active point and wall when clicking on a room, even if + * it's already active. Also force a toolbar update. This is a little hacky + * but works. + */ + if (this.state_.active_point_entity !== undefined) { + this.state_.active_point_entity.set_active(false); + this.floor_plan_.update_toolbar(); + } + if (this.state_.active_wall_entity !== undefined) { + this.state_.active_wall_entity.set_active(false); + this.floor_plan_.update_toolbar(); + } + if (active !== this.active_) { this.active_ = active; @@ -270,13 +274,13 @@ beestat.component.floor_plan_entity.room.prototype.set_active = function(active) if (this.state_.active_point_entity !== undefined) { this.state_.active_point_entity.set_active(false); } + + this.dispatchEvent('inactivate'); } if (this.rendered_ === true) { this.rerender(); } - - this.floor_plan_.update_toolbar(); } return this; @@ -337,6 +341,11 @@ beestat.component.floor_plan_entity.room.prototype.set_room = function(room) { this.x_ = room.x; this.y_ = room.y; + // Ensure a UUID is set on the room. + if (this.room_.room_id === undefined) { + this.room_.room_id = window.crypto.randomUUID(); + } + return this; }; @@ -359,10 +368,15 @@ beestat.component.floor_plan_entity.room.prototype.set_group = function(group) { * * @param {number} x The x position of this entity. * @param {number} y The y position of this entity. + * @param {string} event Optional event to fire when done. * * @return {beestat.component.floor_plan_entity} This. */ -beestat.component.floor_plan_entity.room.prototype.set_xy = function(x, y) { +beestat.component.floor_plan_entity.room.prototype.set_xy = function(x, y, event = 'lesser_update') { + if (event === 'update') { + this.floor_plan_.save_buffer(); + } + let clamped_x = x; let clamped_y = y; @@ -382,7 +396,7 @@ beestat.component.floor_plan_entity.room.prototype.set_xy = function(x, y) { this.room_.x = Math.round(clamped_x); this.room_.y = Math.round(clamped_y); - this.dispatchEvent('update'); + this.dispatchEvent(event); return beestat.component.floor_plan_entity.prototype.set_xy.apply( this, @@ -485,7 +499,6 @@ beestat.component.floor_plan_entity.room.prototype.after_mouseup_handler_ = func if (this.dragged_ === true) { this.clear_snap_lines_(); this.update_snap_points_(); - this.dispatchEvent('update'); } }; diff --git a/js/component/floor_plan_entity/wall.js b/js/component/floor_plan_entity/wall.js index 868b7d1..7e61980 100644 --- a/js/component/floor_plan_entity/wall.js +++ b/js/component/floor_plan_entity/wall.js @@ -65,6 +65,8 @@ beestat.component.floor_plan_entity.wall.prototype.decorate_line_ = function(par * @param {Event} e */ beestat.component.floor_plan_entity.wall.prototype.add_point = function(e) { + this.floor_plan_.save_buffer(); + const room = this.room_.get_room(); for (let i = 0; i < room.points.length; i++) { if (this.point_1_ === room.points[i]) { @@ -303,10 +305,15 @@ beestat.component.floor_plan_entity.wall.prototype.set_room = function(room) { * * @param {number} x The x position of this entity. * @param {number} y The y position of this entity. + * @param {string} event Optional event to fire when done. * * @return {beestat.component.floor_plan_entity.wall} This. */ -beestat.component.floor_plan_entity.wall.prototype.set_xy = function(x, y) { +beestat.component.floor_plan_entity.wall.prototype.set_xy = function(x, y, event = 'lesser_update') { + if (event === 'update') { + this.floor_plan_.save_buffer(); + } + let clamped_x = x + this.room_.get_x(); let clamped_y = y + this.room_.get_y(); @@ -326,7 +333,7 @@ beestat.component.floor_plan_entity.wall.prototype.set_xy = function(x, y) { this.point_2_.y = Math.round(clamped_y - this.room_.get_y()); } - this.dispatchEvent('update'); + this.dispatchEvent(event); return this; }; @@ -568,6 +575,8 @@ beestat.component.floor_plan_entity.wall.prototype.set_active = function(active) this.dispatchEvent('activate'); } else { delete this.state_.active_wall_entity; + + this.dispatchEvent('inactivate'); } if (this.rendered_ === true) {